skimemo


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

_ XAMLをincludeしてイベントを上に上げる

MicroTwitの機能を拡張していくにつれ、XAMLがどんどん長くなってきたので、UserControlを作成してincludeする形にしました。
そのとき、イベントの取り回しで色々苦労したのでφ(.. )です。

以下の3つについて記述します。

  1. XAMLをincludeする
  2. イベントをMainWindowに上げる
  3. MainWindowのXAMLからUserControlに静的に値を渡す

まずはいきなりソース全体像から。

■UserInformation.xaml

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19


■UserInformation.xaml.cs

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
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 = 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

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22


■MainWindow.xaml.cs

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
        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する

やっていることはまんまこちらのページで紹介されている内容です。→ XAML を分割して記述する方法
UserInformation.xaml.csのnamespaceの指定だけ、注意が必要です。

_ イベントをMainWindowに上げる

イベントをユーザーコントロールから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に値を渡す

XAMLを何カ所かでincludeしても、一部で異なる処理をしたい場合があります。
その際は、カスタムプロパティを作ってMainWindow.xamlから指定してやることで、プロパティの内容によって処理を分けることができます。
上の例では、カスタムプロパティ「Mode」を設け、MainWindow.xamlで異なる値を指定しています(10,17行目)。
UserInformation.xaml.csでは、指定されたModeの値をクラス変数modeに保存しておき、「ctxContextMenu_Opening」の中で判別しています。この例では、modeAのときはコンテキストメニューが表示されますが、modeBの時は表示されません。

senderの扱いがちょっとお行儀の悪い感じですが、とりあえず出来ているので良しとします^^;。
イベントが短時間に連続して発生した場合、MainWindow側で正しい値が取れない場合があるかもしれません。
もっと良い方法無いですかねぇ。。

Category: [CSharp][Windows] - 23:25:32



 
Last-modified: 2016-06-06 (月) 00:51:42 (917d)