skimemo


skimemo - 日記/2016-06-05/XAMLをincludeしてイベントを上に上げる の変更点


#blog2navi()
*XAMLをincludeしてイベントを上に上げる [#k5a65819]

MicroTwitの機能を拡張していくにつれ、XAMLがどんどん長くなってきたので、UserControlを作成してincludeする形にしました。~
そのとき、イベントの取り回しで色々苦労したのでφ(.. )です。~
~
以下の3つについて記述します。~
+ XAMLをincludeする
+ イベントをMainWindowに上げる
+ MainWindowのXAMLからUserControlに値を渡す
+ MainWindowのXAMLからUserControlに静的に値を渡す

まずはいきなりソース全体像から。~
~
■UserInformation.xaml
#code(xml,nooutline){{
<UserControl x:Class="MicroTwit.Views.UserInformation"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <TextBlock Text="テキスト文字列" 
            ContextMenuOpening="ctxContextMenu_Opening" 
            PreviewMouseRightButtonDown="txtPreviewMouseRightButtonDown">
            <TextBlock.ContextMenu>
                <ContextMenu Opened="ctxContextMenu_Opened">
                    <MenuItem Header="メニュー項目1" Click="MenuItem1_Click" />
                </ContextMenu>
            </TextBlock.ContextMenu>
        </TextBlock>
    </Grid>
</UserControl>
}}
~
■UserInformation.xaml.cs
#code(CSharp,nooutline){{
using System.Windows.Controls;
using System.Windows;
using System;

namespace MyProject.Views {
    /// <summary>
    /// UserInformation.xaml の相互作用ロジック
    /// </summary>
    public partial class UserInformation : UserControl {

        public object sender;
        private static string mode;

        // イベントRouteの定義
        public static readonly RoutedEvent evt_MouseRightOnText = EventManager.RegisterRoutedEvent("EventMouseRightClickOnText", RoutingStrategy.Bubble, typeof(MouseButtonEventHandler), typeof(TextBox));
        public static readonly RoutedEvent evt_ctxContextMenu_Opening = EventManager.RegisterRoutedEvent("EventContextMenu_Opening", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ContextMenu));
        public static readonly RoutedEvent evt_ctxContextMenu_Opened = EventManager.RegisterRoutedEvent("EventContextMenu_Opened", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ContextMenu));
        public static readonly RoutedEvent evt_MenuItem1_Click = EventManager.RegisterRoutedEvent("EventMenuItem1_Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MenuItem));

        // カスタムプロパティ定義
        internal static readonly DependencyProperty ModeProperty = DependencyProperty.Register("Mode", typeof(string), typeof(UserInformation), new PropertyMetadata(null, new PropertyChangedCallback(OnTweetModeChanged)));
        internal string Mode {
            get { return (string)GetValue(ModeProperty); }
            set { SetValue(ModeProperty, value); }
        }

        // イベント登録
        public event MouseButtonEventHandler EventMouseRightClickOnText{
            add { AddHandler(evt_MouseRightOnText, value); }
            remove { RemoveHandler(evt_MouseRightOnText, value); }
        }
        public event RoutedEventHandler EventContextMenu_Opening {
            add { AddHandler(evt_ctxContextMenu_Opening, value); }
            remove { RemoveHandler(evt_ctxContextMenu_Opening, value); }
        }
        public event RoutedEventHandler EventContextMenu_Opened {
            add { AddHandler(evt_ctxContextMenu_Opened, value); }
            remove { RemoveHandler(evt_ctxContextMenu_Opened, value); }
        }
        public event RoutedEventHandler EventMenuItem1_Click {
            add { AddHandler(evt_MenuItem1_Click, value); }
            remove { RemoveHandler(evt_MenuItem1_Click, value); }
        }

        // コンストラクタ
        public UserInformation() {
            InitializeComponent();
        }

        // イベントを上に上げる処理
        private void txtPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) {
            MouseButtonEventArgs eventArgs = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, e.ChangedButton) { 
                RoutedEvent = evtMouseRightOnText,
                RoutedEvent = evt_MouseRightOnText,
            };
            this.sender = sender;
            RaiseEvent(eventArgs);
        }
        private void ctxContextMenu_Opening(object sender, ContextMenuEventArgs e) {
            if (mode.Equals("modeB")) {
                // コンテキストメニューキャンセル
                e.Handled = true;
            } else {
                RoutedEventArgs eventArgs = new RoutedEventArgs(evt_ctxContextMenu_Opening);
                this.sender = sender;
                RaiseEvent(eventArgs);
            }
        }
        private void ctxContextMenu_Opened(object sender, RoutedEventArgs e) {
            RoutedEventArgs eventArgs = new RoutedEventArgs(evt_ContextMenu_Opened);
            this.sender = sender;
            RaiseEvent(eventArgs);
        }
        private void MenuItem1_Click(object sender, RoutedEventArgs e) {
            RoutedEventArgs eventArgs = new RoutedEventArgs(evt_MenuItem1_Click);
            this.sender = sender;
            RaiseEvent(eventArgs);
        }

        // モードプロパティの変更
        private static void OnModeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {
            UserInformation ui = (UserInformation)obj;
            mode = (string)args.NewValue;
        }
    }
}

}}
~
■MainWindow.xaml
#code(xml, nooutline){{
<Window x:Class="MyProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:shell="http://schemas.microsoft.com/winfx/2006/xaml/presentation/shell"
        xmlns:p="clr-namespace:MyProject.Properties"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:v="clr-namespace:MyProject.Views">
    <!-- user情報用の行を定義 -->
    <v:UserInformation 
        TweetMode="Timeline"
        Mode="modeA"
        EventMouseRightClickOnText="userinfo_MouseRightClickOnText_Click"
        EventContextMenu_Opening="userinfo_ContextMenu_Opening"
        EventContextMenu_Opened="userinfo_ContextMenu_Opened"
        EventMenuItem1_Click="userinfo_MenuItem1_Click"
    />
    <v:UserInformation 
        Mode="modeB"
        EventMouseRightClickOnText="userinfo_MouseRightClickOnText_Click"
        EventContextMenu_Opening="userinfo_ContextMenu_Opening"
        EventContextMenu_Opened="userinfo_ContextMenu_Opened"
        EventMenuItem1_Click="userinfo_MenuItem1_Click"
    />
}}
~
■MainWindow.xaml.cs
#code(CSharp,nooutline){{
        private void userinfo_MouseRightClickOnText_Click(object sender, MouseButtonEventArgs e) {
            object s = ((UserInformation)sender).sender;
            ・・・色々処理・・・
        }
        private void userinfo_ContextMenu_Opened(object sender, RoutedEventArgs e) {
            object s = ((UserInformation)sender).sender;
            ・・・色々処理・・・
        }


}}
では上から順に。
** XAMLをincludeする [#yb7bdb57]
やっていることはまんまこちらのページで紹介されている内容です。→
[[XAML を分割して記述する方法:http://foreignkey.jp/archives/315]]~
ここでの注意点は、namespaceに「MyProject.Views」と指定しているので、一緒に作られるMyProject.xaml.csも以下のようにする必要があることです。
UserInformation.xaml.csのnamespaceの指定だけ、注意が必要です。~
** イベントをMainWindowに上げる [#ra1c0212]
イベントをユーザーコントロールからMainWindowに上げるには、RoutedEventの仕組みを使います。~
「RoutedEventの定義」「イベント登録」「イベントの転送」の三点セットです。~
ここでの注意点は「RoutedEventArgs」と「sender」「MainWindow.xamlのイベント定義」の3つです。~
- RoutedEventArgs~
検索して出てくるサンプルは、RoutedEventArgsを使ったものが殆どですが、Mouseのイベント処理などではマウスのPositionなど、MouseButtonEventArgsが必要になります。evt_MouseRightOnText〜EventMouseRightClickOnText〜txtPreviewMouseRightButtonDownではその例を示しています。
- sender~
MainWindow側のイベント処理では、senderに「UserInformation」が入ってきてしまい、実際にイベントが発生したオブジェクトは(私が調べた限りでは)取ることができません。これを回避するため、UserInformationにクラス変数としてsenderを用意し、そこにイベント発生元のsenderセットすることで、MainWindowから使えるようにしています。
- MainWindow.xamlのイベント定義~
ユーザーコントロールから上がってきたイベントと、MainWindow.xaml.csのイベント処理を紐付ける記述が、MainWindow.xamlのイベント定義です。イベントの数だけ並べてあげる必要があります。
** MainWindowのXAMLからUserControlに値を渡す [#fe91a948]
XAMLを何カ所かでincludeしても、一部で異なる処理をしたい場合があります。~
その際は、カスタムプロパティを作ってMainWindow.xamlから指定してやることで、プロパティの内容によって処理を分けることができます。~
上の例では、カスタムプロパティ「Mode」を設け、MainWindow.xamlで異なる値を指定しています(10,17行目)。~
UserInformation.xaml.csでは、指定されたModeの値をクラス変数modeに保存しておき、「ctxContextMenu_Opening」の中で判別しています。この例では、modeAのときはコンテキストメニューが表示されますが、modeBの時は表示されません。~
~
~
書きかけ〜・・・
senderの扱いがちょっとお行儀の悪い感じですが、とりあえず出来ているので良しとします^^;。~
イベントが短時間に連続して発生した場合、MainWindow側で正しい値が取れない場合があるかもしれません。~
もっと良い方法無いですかねぇ。。

#htmlinsert(twitterbutton.html)
RIGHT:Category: &#x5b;[[CSharp>日記/Category/CSharp]]&#x5d;&#x5b;[[Windows>日記/Category/Windows]]&#x5d; - 23:25:32
----
RIGHT:&blog2trackback();
#comment(above)
#blog2navi()