WPF Event 在 Command 中的应用初级篇,支持所有Event 展示松耦合设计的全部代码 - 解决TextBoxBase.TextChanged或者TextBox.TextChanged等类似事件绑定问题。

简介: 原文:WPF Event 在 Command 中的应用初级篇,支持所有Event 展示松耦合设计的全部代码 - 解决TextBoxBase.TextChanged或者TextBox.TextChanged等类似事件绑定问题。
原文: WPF Event 在 Command 中的应用初级篇,支持所有Event 展示松耦合设计的全部代码 - 解决TextBoxBase.TextChanged或者TextBox.TextChanged等类似事件绑定问题。

做过WPF开发的人,都知道做MVVM架构,最麻烦的是Event的绑定,因为Event是不能被绑定的,同时现有的条件下,命令是无法替代Event。而在开发过程中无法避免Event事件,这样MVVM的架构就不能完全实现了。

所以后来微软提供了一个折中的方案,使用Trigger触发器和System.Windows.Interactivity结合通过事件绑定事件,个人觉得这个方法也挺可以的。

还有Prism框架方法,我看过但是这个方法比较繁琐可用性不高。

后来通过搜索和自己研究知道了一个解决方案,可以绑定所有事件,这个是原始版本,我个人正在研究一个完全的版本,希望能完全支持Event属性。

对于编程学习喜欢的方式,我个人是这个看法,我做开发也有一年多了,WPF开发也有九个月了。从小白到现在入门略有深入。不再局限于实现效果,而是更喜欢实现功能的原汁原味的代码。很多效果也许我不能写出完整的代码,也没有那么多的时间,但是深入看了别人的设计代码,总是受益良多。

工作这么长时间,总算明白为什么Java那么受欢迎了。因为作为技术人员到了一定境界,更喜欢了解具体的实现过程,而对于直接使用SDK的兴趣淡了不少。打个比方微软提供了很多优秀的SDK,但是就像家庭一样,越是包办一切的好父母,往往不太招孩子们的喜欢,不是孩子们不懂得感恩,而是社会自然规律,你无法成长,你便很容易感觉到自己无能,无能的人哪有快乐而言。

正文:具体的类图 通过类图可以看到结构相当清晰,它们的依赖关系是单向的。是属于低耦合的关系

 

 
现在提供各个类的代码,从上到下,层次从左到右。注意实际上建立类的顺序是相反的,这里只是为了展示方便
 
Events.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;


namespace ManMonthMyth.Practices.Composite.Presentation.Behaviors
{
    /// <summary>
    /// 这个类的作用是真正的绑定命令
    /// 将命令绑定到对应的控件上
    /// </summary>
    public class Events
    {
        /// <summary>
        /// 事件行为属性
        /// 注册属性名
        /// </summary>
        private static readonly DependencyProperty EventBehaviorsProperty = DependencyProperty.RegisterAttached("EventBehaviors",typeof(EventBehaviorCollection),typeof(Control),null);

        private static readonly DependencyProperty InternalDataContextProperty = DependencyProperty.RegisterAttached("InternalDataContext", typeof(Object), typeof(Control), new PropertyMetadata(null, DataContextChanged));

        public static readonly DependencyProperty CommandsProperty =
            DependencyProperty.RegisterAttached("Commands", typeof(EventCommandCollection), typeof(Events), new PropertyMetadata(null, CommandsChanged));
        
        private static void DataContextChanged(DependencyObject dependencyObject,DependencyPropertyChangedEventArgs e)
        {
            var target = dependencyObject as Control;
            if (target == null) return;
            foreach (var behavior in GetOrCreateBehavior(target))
            {
                behavior.Bind();
            }
        }

        public static EventCommandCollection GetCommands(DependencyObject dependencyObject)
        {
            return dependencyObject.GetValue(CommandsProperty) as EventCommandCollection;
        }

        public static void SetCommands(DependencyObject dependencyObject,EventCommandCollection eventCommands)
        {
            dependencyObject.SetValue(CommandsProperty,eventCommands);
        }

        private static void CommandsChanged(DependencyObject dependencyObject,DependencyPropertyChangedEventArgs e)
        {
            var target = dependencyObject as Control;
            if (target == null) return;
            var behaviors = GetOrCreateBehavior(target);
            foreach (var eventCommand in e.NewValue as EventCommandCollection)
            {
                var behavior = new EventBehavior(target);
                behavior.Bind(eventCommand);
                behaviors.Add(behavior);
            }
        }

        private static EventBehaviorCollection GetOrCreateBehavior(FrameworkElement target)
        {
            var behavior = target.GetValue(EventBehaviorsProperty) as EventBehaviorCollection;
            if (behavior == null)
            {
                behavior = new EventBehaviorCollection();
                target.SetValue(EventBehaviorsProperty,behavior);
                target.SetBinding(InternalDataContextProperty,new Binding());
            }
            return behavior;
        }

    }
}
View Code


EventBehaviorCollection

using System;
using System.Collections.Generic;

namespace ManMonthMyth.Practices.Composite.Presentation.Behaviors
{
    /// <summary>
    /// 事件名称集合
    /// </summary>
    public class EventCommandCollection : List<EventCommand>
    {

    }
}
View Code


EventBehavior

using System;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace ManMonthMyth.Practices.Composite.Presentation.Behaviors
{
    /// <summary>
    /// 事件行为
    /// 这个类的作用是将,通过反射将事件名,实例化一个真正的事件对象
    /// </summary>
    public class EventBehavior : CommandBehaviorBase<Control>
    {
        private EventCommand _bindingInfo;

        public EventBehavior(Control control) : base(control) { }

        public void Bind(EventCommand bindingInfo)
        {
            ValidateBindingInfo(bindingInfo);
            _bindingInfo = bindingInfo;
            Bind();
        }

        private void ValidateBindingInfo(EventCommand bindingInfo)
        {
            if (bindingInfo == null) throw new ArgumentException("bindingInfo is Null");
            if (string.IsNullOrEmpty(bindingInfo.CommandName)) throw new ArgumentException("bindingInfo.CommandName is Null");
            if (string.IsNullOrEmpty(bindingInfo.EventName)) throw new ArgumentException("bindingInfo.EventName is Null");
        }

        public void Bind() {
            ValidateBindingInfo(_bindingInfo);
            HookPropertyChanged();
            HookEvent();
            SetCommand();
        }

        public void HookPropertyChanged() {
            var dataContext = this.TargetObject.DataContext as INotifyPropertyChanged;
            if (dataContext == null) return;
            dataContext.PropertyChanged -= this.DataContextPropertyChanged;
            dataContext.PropertyChanged += this.DataContextPropertyChanged;
        }

        private void DataContextPropertyChanged(object sender,PropertyChangedEventArgs e)
        {
            if (e.PropertyName == _bindingInfo.CommandName)
                this.SetCommand();
        }

        private void SetCommand()
        {
            var dataContext = this.TargetObject.DataContext;
            if (dataContext == null) return;
            var propertyInfo = dataContext.GetType().GetProperty(_bindingInfo.CommandName);
            if (propertyInfo == null) throw new ArgumentException("propertyInfo is null,commandName is error");
            this.Command = propertyInfo.GetValue(dataContext,null) as ICommand;
        }
        /// <summary>
        /// 钩事件
        /// 有助于绑定AttachedProperty属性
        /// </summary>
        private void HookEvent()
        {
            var eventNames = _bindingInfo.EventName.Split('.');
            int length = eventNames.Length;
            //分开来,目的是为了提高效率
            if (length == 1)
                {
                    var eventInfo = this.TargetObject.GetType().GetEvent(_bindingInfo.EventName, BindingFlags.Public | BindingFlags.Instance);
                    if (eventInfo == null) throw new ArgumentException("eventInfo is Null,eventName is error");
                    eventInfo.RemoveEventHandler(this.TargetObject, this.GetEventMethod(eventInfo));
                    eventInfo.AddEventHandler(this.TargetObject, GetEventMethod(eventInfo));
                }
                else
                {
                    //TargetObject控件加载完成后触发执行
                    this.TargetObject.Initialized += (sender, e) =>
                    {
                        var tempObject = SelectVisual(eventNames, this.TargetObject);
                        var tempEventInfo = tempObject.GetType().GetEvent(eventNames[length-1], BindingFlags.Public | BindingFlags.Instance);
                        if (tempEventInfo == null) throw new ArgumentException("eventInfo is Null,eventName is error");
                        tempEventInfo.RemoveEventHandler(tempObject, this.GetEventMethod(tempEventInfo));
                        tempEventInfo.AddEventHandler(tempObject, GetEventMethod(tempEventInfo));
                    };
                } 
        }

        #region 筛选内部控件

        /// <summary>
        /// 筛选Visual
        /// </summary>
        /// <param name="names"></param>
        /// <param name="obj"></param>
        /// <returns></returns>
        private static Visual SelectVisual(string[] names, Control obj)
        {
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            int act = assemblies.Length;
            for (int i = 0; i < act; i++)
            {
                if (assemblies[i].FullName.Split(',')[0] == "PresentationFramework")
                {
                    return SelectVisual(assemblies[i], names, 0, obj);
                }
            }
             return null;
        }

        /// <summary>
        /// 命名空间名称集合
        /// </summary>
        static string[] namespaceNames = {"System.Windows.Controls.Primitives","System.Windows.Controls" };
        /// <summary>
        /// 筛选Visual
        /// </summary>
        /// <param name="assembly"></param>
        /// <param name="names"></param>
        /// <param name="i"></param>
        /// <param name="obj"></param>
        /// <returns></returns>
        private static Visual SelectVisual(Assembly assembly, string[] names,int i,Visual obj)
        {
            Type type=null;
            int tempLength=namespaceNames.Length;
            for (int j = 0; j < tempLength; j++)
            {
                type = assembly.GetType(string.Format("{0}.{1}",namespaceNames[j], names[i]));
                if(type!=null)
                {
                    break;
                }
            }
            if (type == null)
            {
                return null;
            }
            obj = GetDescendantByType(obj, type);
            if (names.Length == (i+2))//判断是否到了数组有效的位置,因为为了偷懒没有对string[]进行去尾操作,因为string[]最后一个数据是事件名称,在这里是 无效的
            {
                return obj;
            }
           return  SelectVisual(assembly, names, i+1, obj);
        }

        /// <summary>
        /// 通过Type获取包含的控件
        /// </summary>
        /// <param name="element"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        private static Visual GetDescendantByType(Visual element, Type type)
        {
            if (element == null) return null;
       
            if (element.GetType() == type||element.GetType().BaseType==type) return element;
            Visual foundElement = null;
            if (element is FrameworkElement)
                (element as FrameworkElement).ApplyTemplate();
            int cnt = VisualTreeHelper.GetChildrenCount(element);
            for (int i = 0; i < cnt; i++)
            {
                Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
                foundElement = GetDescendantByType(visual, type);
                if (foundElement != null)
                    break;
            }
            return foundElement;
        }

        #endregion




        private Delegate _method;
        private Delegate GetEventMethod(EventInfo eventInfo)
        {
            if (eventInfo == null) throw new ArgumentException("eventInfo is null");
            if (eventInfo.EventHandlerType == null) throw new ArgumentException("EventHandlerType is null");
            if (this._method == null)
            {
                this._method = Delegate.CreateDelegate(
                    eventInfo.EventHandlerType,this,GetType().GetMethod("OnEventRaised",BindingFlags.NonPublic|BindingFlags.Instance));
            }
            return _method;
        }

        private void OnEventRaised(object sender,EventArgs e)
        {
            this.ExecuteCommand();
        }

    }
}
View Code


EventCommandCollection

using System;
using System.Collections.Generic;

namespace ManMonthMyth.Practices.Composite.Presentation.Behaviors
{
    /// <summary>
    /// 事件名称集合
    /// </summary>
    public class EventCommandCollection : List<EventCommand>
    {

    }
}
View Code


CommandBehaviorBase

using System;
using System.Windows.Controls;
using System.Windows.Input;

namespace ManMonthMyth.Practices.Composite.Presentation.Behaviors
{
    /// <summary>
    /// 基于行为的处理连接的一个命令
    /// 这个类可以用来提供新的行为,比如按钮的Clik事件行为
    /// 鼠标进入控件的事件行为
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class CommandBehaviorBase<T> where T : Control
    {
        /// <summary>
        /// 定义的一个命令
        /// </summary>
        private ICommand command;
        /// <summary>
        /// 命令传值的内容
        /// </summary>
        private object commandParameter;
        /// <summary>
        /// 弱引用对象
        /// 即对象被引用的情况下,允许垃圾回收来回收对象
        /// </summary>
        private readonly WeakReference targetObject;
        /// <summary>
        /// 命令中允许事件发生的事件的方法,
        /// 即允许事件发生的条件
        /// </summary>
        private readonly EventHandler commandCanExecuteChangedHandler;

        #region 属性
        /// <summary>
        /// 相应的命令被执行和监控
        /// </summary>
        public ICommand Command
        {
            get { return this.command; }
            set {
                if (this.command != null)
                {
                    this.command.CanExecuteChanged -= this.commandCanExecuteChangedHandler;
                }
                this.command = value;
                if (this.command != null)
                {
                    this.command.CanExecuteChanged += this.commandCanExecuteChangedHandler;
                    this.UpdateEnabledState();
                }
            }
        }
        /// <summary>
        /// 命令中传值的内容
        /// </summary>
        public object CommandParameter
        {
            get { return this.commandParameter; }
            set { if (this.commandParameter != value) {
                this.commandParameter = value;
                this.UpdateEnabledState();
            } }
        }
        /// <summary>
        /// 一个弱引用的对象
        /// </summary>
        protected T TargetObject
        {
            get {
                return targetObject.Target as T;
            }
        }

        #endregion

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="targetObject">行为上的目标对象</param>
        public CommandBehaviorBase(T targetObject)
        {
            this.targetObject = new WeakReference(targetObject);
            this.commandCanExecuteChangedHandler = new EventHandler(this.CommandCanExecuteChanged);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CommandCanExecuteChanged(object sender,EventArgs e)
        {
            this.UpdateEnabledState();
        }

        /// <summary>
        /// 基于属性的命令的执行,来更新的目标对象。
        /// </summary>
        protected virtual void UpdateEnabledState()
        {
            if (this.TargetObject == null)
            {
                this.Command = null;
                this.CommandParameter = null;
            }else if(this.Command!=null)
            {
                this.TargetObject.IsEnabled = this.Command.CanExecute(this.CommandParameter);
            }
        }
        /// <summary>
        /// 执行命令
        /// </summary>
        protected virtual void ExecuteCommand()
        {
            if (this.Command != null)
            {
                this.Command.Execute(this.CommandParameter);
            }
        }

    }
}
View Code


EventCommand

using System;

namespace ManMonthMyth.Practices.Composite.Presentation.Behaviors
{
    public class EventCommand
    {
        /// <summary>
        /// 命令名称
        /// 这个可以自定义,比如myCommandClick等
        /// </summary>
        public string CommandName { get; set; }
        /// <summary>
        /// 特定的事件名称
        /// 首先要确认绑定的控件支持这个事件
        /// 比如Click MouseOver 等
        /// </summary>
        public string EventName { get; set; }

    }
}
View Code

 

现在完整的框架已经搭建好了,现在就是做一个Demo就可以了。

用命令绑定事件,就要考虑到类似TextBoxBase.TextChanged命令的问题,理解这个的前提是了解Attached Property,Attached Property是DependencyObject的一种特殊形式,它利用DependencyProperty.RegisterAttached方法注册的。可以被有效的添加到任何继承自DependencyObject的对象中。同时要明白绑定控件中包含有TextBox这个控件,找到TextBox控件然后绑定它的事件问题就解决了。

说完思路现在提供以上绑事件命令类库如何使用:

  <ComboBox x:Name="cboBox" Margin="115,0,10,0" IsEditable="True" ItemsSource="{Binding ListCollection}" Text="{Binding UserName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"   FontSize="15" Height="25" HorizontalAlignment="Left"  VerticalAlignment="Center" Width="179">
                    <Behaviors:Events.Commands>
                        <Behaviors:EventCommandCollection>
                            <!--<Behaviors:EventCommand  CommandName="cboTextBoxChangedCommand" EventName="TextBox.TextChanged" />-->
                            <Behaviors:EventCommand  CommandName="cboTextBoxBaseChangedCommand" EventName="TextBoxBase.TextChanged" />
                            <Behaviors:EventCommand CommandName="cboSelectionChangedCommand" EventName="SelectionChanged" />
                        </Behaviors:EventCommandCollection>
                    </Behaviors:Events.Commands>
                </ComboBox>
示例代码

运用示例代码来自于这个xaml文件中

MainWindow.xaml

<Window x:Class="EventCommandDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:local="clr-namespace:EventCommandDemo"
         xmlns:Behaviors="clr-namespace:ManMonthMyth.Practices.Composite.Presentation.Behaviors;assembly=ManMonthMyth.Practices.Composite.Presentation.Behaviors"
        Title="MainWindow"  Width="400" Height="300" WindowStyle="None" ResizeMode="NoResize" Background="LightPink" WindowStartupLocation="CenterScreen">
    <Window.Triggers>
        <EventTrigger SourceName="chkRememberPwd" RoutedEvent="CheckBox.Unchecked">
            <BeginStoryboard>
                <Storyboard>
                    <BooleanAnimationUsingKeyFrames  Storyboard.TargetName="chkLanding" Storyboard.TargetProperty="IsChecked">
                        <DiscreteBooleanKeyFrame KeyTime="0:0:0.01" Value="False" />
                    </BooleanAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
        <EventTrigger SourceName="chkLanding" RoutedEvent="CheckBox.Checked">
            <BeginStoryboard>
                <Storyboard>
                    <BooleanAnimationUsingKeyFrames Storyboard.TargetName="chkRememberPwd" Storyboard.TargetProperty="IsChecked">
                        <DiscreteBooleanKeyFrame KeyTime="0:0:0.01" Value="True" />
                    </BooleanAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Window.Triggers>
    <Viewbox>
    <Grid  Width="400" Height="300">
        <Grid.RowDefinitions>
            <RowDefinition Height="120" />
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
            <RowDefinition Height="30" />
            <RowDefinition />
        </Grid.RowDefinitions>  
        <StackPanel Grid.Row="1" Orientation="Horizontal">
                <ComboBox x:Name="cboBox" Margin="115,0,10,0" IsEditable="True" ItemsSource="{Binding ListCollection}" Text="{Binding UserName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"   FontSize="15" Height="25" HorizontalAlignment="Left"  VerticalAlignment="Center" Width="179">
                    <Behaviors:Events.Commands>
                        <Behaviors:EventCommandCollection>
                            <!--<Behaviors:EventCommand  CommandName="cboTextBoxChangedCommand" EventName="TextBox.TextChanged" />-->
                            <Behaviors:EventCommand  CommandName="cboTextBoxBaseChangedCommand" EventName="TextBoxBase.TextChanged" />
                            <Behaviors:EventCommand CommandName="cboSelectionChangedCommand" EventName="SelectionChanged" />
                        </Behaviors:EventCommandCollection>
                    </Behaviors:Events.Commands>
                </ComboBox>
            <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Text="注册账号" >
                <Behaviors:Events.Commands>
                    <Behaviors:EventCommandCollection>
                        <Behaviors:EventCommand CommandName="TxTMouseEnterCommand" EventName="MouseEnter" />
                    </Behaviors:EventCommandCollection>
                </Behaviors:Events.Commands>
            </TextBlock>
        </StackPanel>
        <StackPanel Grid.Row="2" Orientation="Horizontal">
                <PasswordBox Margin="115,0,10,0" local:PasswordBoxBindingHelper.IsPasswordBindingEnabled="True"  local:PasswordBoxBindingHelper.BindedPassword="{Binding Path=UserPassword,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" FontSize="15" Height="25" HorizontalAlignment="Left" VerticalAlignment="Center" Width="179" >
                    <Behaviors:Events.Commands>
                        <Behaviors:EventCommandCollection>
                            <Behaviors:EventCommand CommandName="pwdGotFocusCommand" EventName="GotFocus" />
                        </Behaviors:EventCommandCollection>
                    </Behaviors:Events.Commands>
                </PasswordBox>
                <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center"  Name="textBlock2" Text="忘记密码" Style="{Binding ElementName=textBlock1,Path=Style}" />
        </StackPanel>
        <StackPanel Grid.Row="3" Orientation="Horizontal">
                <CheckBox x:Name="chkRememberPwd"  Content="记住密码" IsThreeState="False" HorizontalAlignment="Left" Margin="115,0,0,0" VerticalAlignment="Top" >
                    <Behaviors:Events.Commands>
                        <Behaviors:EventCommandCollection>
                            <Behaviors:EventCommand CommandName="chkUncheckedCommand" EventName="Unchecked"/>
                            <Behaviors:EventCommand CommandName="chkCheckedCommand" EventName="Checked" />
                        </Behaviors:EventCommandCollection>
                    </Behaviors:Events.Commands>
                </CheckBox>
                <CheckBox x:Name="chkLanding" Content="自动登陆"  IsThreeState="False" HorizontalAlignment="Left" Margin="15,0,0,0" VerticalAlignment="Top" />
        </StackPanel>
            <StackPanel Grid.Row="4" Orientation="Horizontal">
                <Button Margin="120,0,0,0" Height="33" Width="181" Name="btnLogin" VerticalAlignment="Top">
                    <TextBlock FontSize="18" FontFamily="Consolas;Microsoft YaHei" Foreground="Blue" Text="登    录" />
                    <Behaviors:Events.Commands>
                        <Behaviors:EventCommandCollection>
                            <!--<Behaviors:EventCommand CommandName="btnMouseDoubleClickCommand" EventName="MouseDoubleClick" />-->
                            <Behaviors:EventCommand CommandName="btnClickCommand" EventName="Click" />
                        </Behaviors:EventCommandCollection>
                    </Behaviors:Events.Commands>
                </Button>
            </StackPanel>
    </Grid>
    </Viewbox>
</Window>
MainWindow.xaml

运行后的界面

 

MainWindow.xaml 后台代码 MainWindow.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace EventCommandDemo
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            #region 绑定数据和命令
            MainViewModel _viewmodel = null;
            this.Loaded += (sender, e) =>
            {
                if (_viewmodel == null)
                {
                    _viewmodel = new MainViewModel();
                    this.DataContext = _viewmodel;
                }
            };
            #endregion
        }

      }
}
MainWindow.cs

Mainwindow.xaml辅助类 PasswordBoxBindingHelper.cs,提供PasswordBox绑定属性

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace EventCommandDemo
{
    public static class PasswordBoxBindingHelper
    {
            public static bool GetIsPasswordBindingEnabled(DependencyObject obj)
            {
                return (bool)obj.GetValue(IsPasswordBindingEnabledProperty);
            }

            public static void SetIsPasswordBindingEnabled(DependencyObject obj, bool value)
            {
                obj.SetValue(IsPasswordBindingEnabledProperty, value);
            }

            public static readonly DependencyProperty IsPasswordBindingEnabledProperty = DependencyProperty.RegisterAttached("IsPasswordBindingEnabled", typeof(bool),
                typeof(PasswordBoxBindingHelper),new UIPropertyMetadata(false,OnIsPasswordBindingEnabledChanged));

            private static void OnIsPasswordBindingEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
            {
                var passwordBox = obj as System.Windows.Controls.PasswordBox;
                if (passwordBox != null)
                {
                    passwordBox.PasswordChanged -= PasswordBoxPasswordChanged;
                    if ((bool)e.NewValue)
                    {
                        passwordBox.PasswordChanged += PasswordBoxPasswordChanged;
                    }
                }
            }

            static void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e)
            {
                var passwordBox = (System.Windows.Controls.PasswordBox)sender;
                if (!String.Equals(GetBindedPassword(passwordBox), passwordBox.Password))
                {
                    SetBindedPassword(passwordBox, passwordBox.Password);
                    //目的是让打字的光标显示在最后
                    int length = passwordBox.Password.Length;
                    SetPasswordBoxSelection(passwordBox, length, length);
                }
            }

            public static string GetBindedPassword(DependencyObject obj)
            {
                return (string)obj.GetValue(BindedPasswordProperty);
            }

            public static void SetBindedPassword(DependencyObject obj, string value)
            {
                obj.SetValue(BindedPasswordProperty, value);
            }

            public static readonly DependencyProperty BindedPasswordProperty = DependencyProperty.RegisterAttached("BindedPassword", typeof(string),
                typeof(PasswordBoxBindingHelper),new UIPropertyMetadata(string.Empty, OnBindedPasswordChanged));

            private static void OnBindedPasswordChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
            {
                var passwordBox = obj as System.Windows.Controls.PasswordBox;
                if (passwordBox != null)
                {
                    passwordBox.Password = e.NewValue == null ? string.Empty : e.NewValue.ToString();
                }
            }

            /// <summary>
            /// 在更改了密码框的密码后, 需要手动更新密码框插入符(CaretIndex)的位置, 可惜的是, 
            /// 密码框并没有给我们提供这样的属性或方法(TextBox有, PasswordBox没有), 
            /// 可以采用下面的方法来设置
            /// </summary>
            /// <param name="passwordBox"></param>
            /// <param name="start"></param>
            /// <param name="length"></param>
            private static void SetPasswordBoxSelection(System.Windows.Controls.PasswordBox passwordBox, int start, int length)
            {
                var select = passwordBox.GetType().GetMethod("Select",
                                System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);

                select.Invoke(passwordBox, new object[] { start, length });
            }


        }
}
PasswordBoxBindingHelper.cs

MainWindow的ViewModel文件MainViewModel.cs 提供绑定数据和命令及命令实现方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows;

namespace EventCommandDemo
{
    public class MainViewModel : ViewModelBase
    {

        public MainViewModel()
        {
            cboMouseEnterCommand = new ViewModelCommand((Object parameter) => { cboMouseEnterEvent(); });
            cboTextBoxChangedCommand = new ViewModelCommand((Object parameter) => { cboTextBoxChangedEvent(); });
            cboTextBoxBaseChangedCommand = new ViewModelCommand((Object parameter) => { cboTextBoxBaseChangedEvent(); });
            cboSelectionChangedCommand = new ViewModelCommand((Object parameter) => { cboSelectionChangedEvent(); });
            btnClickCommand = new ViewModelCommand((Object parameter) => { btnClickEvent(); });
            btnMouseDoubleClickCommand = new ViewModelCommand((Object paramenter) => { btnMouseDoubleClickEvent(); });
            pwdGotFocusCommand = new ViewModelCommand((Object paramenter) => { pwdGotFocusEvent(); });
            chkCheckedCommand = new ViewModelCommand((Object paramenter) => { chkCheckedEvent(); });
            chkUncheckedCommand = new ViewModelCommand((Object paramenter) => { chkUncheckedEvent(); });

            var tempList =new string[]{"一个","两个","三个"};
            ListCollection = tempList;
        }

        #region 事件命令
        /// <summary>
        /// 下拉框 鼠标进入事件命令
        /// </summary>
        public ICommand cboMouseEnterCommand { get; private set; }
        /// <summary>
        /// 下拉框文本改变事件命令,通过TextBox.TextChanged
        /// </summary>
        public ICommand cboTextBoxChangedCommand { get;private set; }
        /// <summary>
        /// 下拉框文本改变事件命令,通过TextBoxBase.TextChanged
        /// </summary>
        public ICommand cboTextBoxBaseChangedCommand { get; private set; }

        /// <summary>
        /// 下拉框,选中事件
        /// </summary>
        public ICommand cboSelectionChangedCommand { get; private set; }

        /// <summary>
        /// 登陆按钮单机事件命令
        /// </summary>
        public ICommand btnClickCommand { get; private set; }

        /// <summary>
        /// 登陆按钮双击事件命令
        /// </summary>
        public ICommand btnMouseDoubleClickCommand { get; private set; }
        /// <summary>
        /// 密码框获取焦点事件命令
        /// </summary>
        public ICommand pwdGotFocusCommand { get; private set; }

        /// <summary>
        /// 多选框非选中事件命令
        /// </summary>
        public ICommand chkUncheckedCommand { get;private set; }
        /// <summary>
        /// 多选框选中事件命令
        /// </summary>
        public ICommand chkCheckedCommand { get;private set; }
     

        #endregion

        #region 事件执行方法
        /// <summary>
        /// 下拉框 鼠标进入事件,执行方法。
        /// </summary>
        private void cboMouseEnterEvent()
        {
            MessageBox.Show("下拉框,鼠标进入事件被触发了");
        }

        /// <summary>
        /// 下拉框文本改变事件,执行方法
        /// </summary>
        private void cboTextBoxBaseChangedEvent()
        {
            MessageBox.Show("TextBoxBase - 用户名输入,下拉框文本改变事件被触发了");
        }

        private void cboTextBoxChangedEvent()
        {
            MessageBox.Show("TextBox - 用户名输入,下拉框文本改变事件被触发了");
        }

        /// <summary>
        /// 下拉框选中事件,执行方法
        /// </summary>
        private void cboSelectionChangedEvent()
        {
            MessageBox.Show("下拉框,下拉框选中一个Item事件被触发了");
        }
        /// <summary>
        /// 登陆按钮单机事件,执行方法
        /// </summary>
        private void btnClickEvent()
        {
            MessageBox.Show("登陆按钮,鼠标单机(Click)事件被触发了");
        }

        /// <summary>
        /// 登陆按钮双击事件,执行方法
        /// </summary>
        private void btnMouseDoubleClickEvent()
        {
            MessageBox.Show("登陆按钮,鼠标双击事件被触发了");
        }

        /// <summary>
        /// 密码框获取焦点事件,执行方法
        /// </summary>
        private void pwdGotFocusEvent()
        {
            MessageBox.Show("密码框获取了焦点,事件被触发了");
        }

        /// <summary>
        /// 多选框,非选中状态事件,执行方法
        /// </summary>
        private void chkUncheckedEvent()
        {
            MessageBox.Show("多选框没选中,事件被触发了");
        }
        /// <summary>
        /// 多选框,选中状态事件,执行方法
        /// </summary>
        private void chkCheckedEvent()
        {
            MessageBox.Show("多选框选中,事件被触发了");
        }
        #endregion

        #region 成员
        private string[] listCollection;
        #endregion

        #region 属性
        public string[] ListCollection { get { return listCollection; } set { this.SetProperty(ref this.listCollection,value);} }
        #endregion

    }
}
MainViewModel.cs

ViewModel辅助类有ViewModelBase.cs,ViewModelCommand.cs 分别提供属性更改通知和Command命令接口ICommand实现类

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;

namespace EventCommandDemo
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        #region 支持.NET4.5
        protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            if (object.Equals(storage, value)) { return false; }
            storage = value;
            this.OnPropertyChanged(propertyName);
            return true;
        }
        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var eventHandler = this.PropertyChanged;
            if (eventHandler != null)
            {
                eventHandler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion
    
        #region 支持.NET4.0

        //private static string GetProperyName(string methodName)
        //{
        //    if (methodName.StartsWith("get_") || methodName.StartsWith("set_") ||
        //        methodName.StartsWith("put_"))
        //    {
        //        return methodName.Substring("get_".Length);
        //    }
        //    throw new Exception(methodName + " not a method of Property");
        //}

        //protected bool SetProperty<T>(ref T storage, T value)
        //{
        //    if (object.Equals(storage, value)) { return false; }
        //    storage = value;
        //    string propertyName = GetProperyName(new System.Diagnostics.StackTrace(true).GetFrame(1).GetMethod().Name);
        //    this.OnPropertyChanged(propertyName);
        //    return true;
        //}

        //private void OnPropertyChanged(string propertyName)
        //{
        //    var eventHandler = this.PropertyChanged;
        //    if (eventHandler != null)
        //    {
        //        eventHandler(this, new PropertyChangedEventArgs(propertyName));
        //    }
        //}

        #endregion
       
    }

}
ViewModelBase.cs

 

using System;
using System.Collections.Generic;
using System.Linq; using System.Text; using System.Threading.Tasks; namespace EventCommandDemo { public class ViewModelCommand : System.Windows.Input.ICommand { private Action<Object> action; public Action<Object> ViewModelAction { get { return action; } set { action = value; } } public ViewModelCommand(Action<Object> act) { action = act; } public bool CanExecute(Object parameter) { return true; } public void Execute(Object parameter) { this.ViewModelAction(parameter); } public event EventHandler CanExecuteChanged { add { } remove { } } } }
ViewModelCommand.cs

 

事件被绑定后触发相关事件界面:

 

这里分享源码EventCommandDemo.zip文件下载地址,希望大家在技术的殿堂里都能快速成长。

 

源码下载:

点击下载EventCommandDemo.zip

                                                                                                                       本文作者夜神,首发博客园,转载请注明。

 

目录
相关文章
|
4月前
|
C# UED 开发者
WPF与性能优化:掌握这些核心技巧,让你的应用从卡顿到丝滑,彻底告别延迟,实现响应速度质的飞跃——从布局到动画全面剖析与实例演示
【8月更文挑战第31天】本文通过对比优化前后的方法,详细探讨了提升WPF应用响应速度的策略。文章首先分析了常见的性能瓶颈,如复杂的XAML布局、耗时的事件处理、不当的数据绑定及繁重的动画效果。接着,通过具体示例展示了如何简化XAML结构、使用后台线程处理事件、调整数据绑定设置以及利用DirectX优化动画,从而有效提升应用性能。通过这些优化措施,WPF应用将更加流畅,用户体验也将得到显著改善。
324 1
|
4月前
|
容器 C# Docker
WPF与容器技术的碰撞:手把手教你Docker化WPF应用,实现跨环境一致性的开发与部署
【8月更文挑战第31天】容器技术简化了软件开发、测试和部署流程,尤其对Windows Presentation Foundation(WPF)应用程序而言,利用Docker能显著提升其可移植性和可维护性。本文通过具体示例代码,详细介绍了如何将WPF应用Docker化的过程,包括创建Dockerfile及构建和运行Docker镜像的步骤。借助容器技术,WPF应用能在任何支持Docker的环境下一致运行,极大地提升了开发效率和部署灵活性。
163 1
|
4月前
|
安全 C# 数据安全/隐私保护
WPF安全加固全攻略:从数据绑定到网络通信,多维度防范让你的应用固若金汤,抵御各类攻击
【8月更文挑战第31天】安全性是WPF应用程序开发中不可或缺的一部分。本文从技术角度探讨了WPF应用面临的多种安全威胁及防护措施。通过严格验证绑定数据、限制资源加载来源、实施基于角色的权限管理和使用加密技术保障网络通信安全,可有效提升应用安全性,增强用户信任。例如,使用HTML编码防止XSS攻击、检查资源签名确保其可信度、定义安全策略限制文件访问权限,以及采用HTTPS和加密算法保护数据传输。这些措施有助于全面保障WPF应用的安全性。
66 0
|
4月前
|
C# 开发者 Windows
全面指南:WPF无障碍设计从入门到精通——让每一个用户都能无障碍地享受你的应用,从自动化属性到焦点导航的最佳实践
【8月更文挑战第31天】为了确保Windows Presentation Foundation (WPF) 应用程序对所有用户都具备无障碍性,开发者需关注无障碍设计原则。这不仅是法律要求,更是社会责任,旨在让技术更人性化,惠及包括视障、听障及行动受限等用户群体。
96 0
|
4月前
|
测试技术 C# 开发者
“代码守护者:详解WPF开发中的单元测试策略与实践——从选择测试框架到编写模拟对象,全方位保障你的应用程序质量”
【8月更文挑战第31天】单元测试是确保软件质量的关键实践,尤其在复杂的WPF应用中更为重要。通过为每个小模块编写独立测试用例,可以验证代码的功能正确性并在早期发现错误。本文将介绍如何在WPF项目中引入单元测试,并通过具体示例演示其实施过程。首先选择合适的测试框架如NUnit或xUnit.net,并利用Moq模拟框架隔离外部依赖。接着,通过一个简单的WPF应用程序示例,展示如何模拟`IUserRepository`接口并验证`MainViewModel`加载用户数据的正确性。这有助于确保代码质量和未来的重构与扩展。
116 0
|
4月前
|
前端开发 C# 设计模式
“深度剖析WPF开发中的设计模式应用:以MVVM为核心,手把手教你重构代码结构,实现软件工程的最佳实践与高效协作”
【8月更文挑战第31天】设计模式是在软件工程中解决常见问题的成熟方案。在WPF开发中,合理应用如MVC、MVVM及工厂模式等能显著提升代码质量和可维护性。本文通过具体案例,详细解析了这些模式的实际应用,特别是MVVM模式如何通过分离UI逻辑与业务逻辑,实现视图与模型的松耦合,从而优化代码结构并提高开发效率。通过示例代码展示了从模型定义、视图模型管理到视图展示的全过程,帮助读者更好地理解并应用这些模式。
128 0
|
4月前
|
存储 C# 关系型数据库
“云端融合:WPF应用无缝对接Azure与AWS——从Blob存储到RDS数据库,全面解析跨平台云服务集成的最佳实践”
【8月更文挑战第31天】本文探讨了如何将Windows Presentation Foundation(WPF)应用与Microsoft Azure和Amazon Web Services(AWS)两大主流云平台无缝集成。通过具体示例代码展示了如何利用Azure Blob Storage存储非结构化数据、Azure Cosmos DB进行分布式数据库操作;同时介绍了如何借助Amazon S3实现大规模数据存储及通过Amazon RDS简化数据库管理。这不仅提升了WPF应用的可扩展性和可用性,还降低了基础设施成本。
98 0
|
7月前
|
C# 开发者 Windows
基于Material Design风格开源、易用、强大的WPF UI控件库
基于Material Design风格开源、易用、强大的WPF UI控件库
404 0
|
7月前
|
C#
浅谈WPF之装饰器实现控件锚点
使用过visio的都知道,在绘制流程图时,当选择或鼠标移动到控件时,都会在控件的四周出现锚点,以便于修改大小,移动位置,或连接线等,那此功能是如何实现的呢?在WPF开发中,想要在控件四周实现锚点,可以通过装饰器来实现,今天通过一个简单的小例子,简述如何在WPF开发中,应用装饰器,仅供学习分享使用,如有不足之处,还请指正。
155 1
|
4月前
|
开发框架 缓存 前端开发
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件