【问题标题】:Applying same interaction trigger to multiple TextBoxes将相同的交互触发器应用于多个文本框
【发布时间】:2011-08-05 13:44:24
【问题描述】:

我有多个文本框绑定到不同的数据,但我希望每个文本框都在触发 TextChanged 事件时启动相同的命令。我可以复制每个文本框下的交互行,但我猜必须有一种方法可以使用模板或样式来使其对所有文本框都有效。

这是第一个文本框的代码

<TextBox Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" Height="28" HorizontalAlignment="Stretch" Margin="0,12,12,0" Name="TextBox_Description" VerticalAlignment="Top" TabIndex="3" Text="{Binding Item.Description, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="TextChanged">
                <i:InvokeCommandAction Command="{Binding DataChangedCommand}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </TextBox>

【问题讨论】:

    标签: xaml mvvm


    【解决方案1】:

    如果您的 UpdateMode 始终是 PropertyChanged,那么确实没有任何理由不在您的 ViewModel 中监听您自己的 PropertyChangedEvent。它肯定会提高可测试性。

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            switch (args.PropertyName)
            {
                case "Prop1":
                case "Prop2":
                    .
                    .
                    .
                    DataChangedCommand.Execute(null);
                    break;
            }
        }
    

    【讨论】:

    • 如果在实体的情况下属性没有改变怎么办。如果我的实体有它自己的属性,并且我绑定了实体的属性,则不会在 ViewModel 中引发更改的属性。我可以以某种方式在 ViewModel 中监听我的实体的属性更改吗?
    • 如果“实体”是指“模型”,那么我肯定会通过视图模型公开所有模型属性。你是这个意思吗?
    【解决方案2】:

    我不确定如何使用 Interaction.Triggers 来实现,但过去我使用了 AttachedCommand 依赖属性找到 here 并简单地使用了样式

    普通 XAML

    <TextBox local:CommandBehavior.Event="TextChanged" 
             local:CommandBehavior.Command="{Binding DataChangedCommand}"/>
    

    使用样式

    <Style TargetType="TextBox">
        <Setter Property="local:CommandBehavior.Event" Value="TextChanged" />
        <Setter Property="local:CommandBehavior.Command" Value="{Binding DataChangedCommand}" />
    </Style>
    

    编辑:由于你不能在工作中下载文件,这里是代码

    CommandBehavior.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Markup;
    using System.Windows;
    using System.Windows.Input;
    
    // Code to attach a command to any event
    // From http://marlongrech.wordpress.com/2008/12/04/attachedcommandbehavior-aka-acb/
    namespace Keys.Controls.Helpers.CommandHelpers
    {
        /// <summary>
        /// Defines the attached properties to create a CommandBehaviorBinding
        /// </summary>
        public class CommandBehavior
        {
            #region Behavior
    
            /// <summary>
            /// Behavior Attached Dependency Property
            /// </summary>
            private static readonly DependencyProperty BehaviorProperty =
                DependencyProperty.RegisterAttached("Behavior", typeof(CommandBehaviorBinding), typeof(CommandBehavior),
                    new FrameworkPropertyMetadata((CommandBehaviorBinding)null));
    
            /// <summary>
            /// Gets the Behavior property. 
            /// </summary>
            private static CommandBehaviorBinding GetBehavior(DependencyObject d)
            {
                return (CommandBehaviorBinding)d.GetValue(BehaviorProperty);
            }
    
            /// <summary>
            /// Sets the Behavior property.  
            /// </summary>
            private static void SetBehavior(DependencyObject d, CommandBehaviorBinding value)
            {
                d.SetValue(BehaviorProperty, value);
            }
    
            #endregion
    
            #region Command
    
            /// <summary>
            /// Command Attached Dependency Property
            /// </summary>
            public static readonly DependencyProperty CommandProperty =
                DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(CommandBehavior),
                    new FrameworkPropertyMetadata((ICommand)null,
                        new PropertyChangedCallback(OnCommandChanged)));
    
            /// <summary>
            /// Gets the Command property.  
            /// </summary>
            public static ICommand GetCommand(DependencyObject d)
            {
                return (ICommand)d.GetValue(CommandProperty);
            }
    
            /// <summary>
            /// Sets the Command property. 
            /// </summary>
            public static void SetCommand(DependencyObject d, ICommand value)
            {
                d.SetValue(CommandProperty, value);
            }
    
            /// <summary>
            /// Handles changes to the Command property.
            /// </summary>
            private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                CommandBehaviorBinding binding = FetchOrCreateBinding(d);
                binding.Command = (ICommand)e.NewValue;
            }
    
            #endregion
    
            #region CommandParameter
    
            /// <summary>
            /// CommandParameter Attached Dependency Property
            /// </summary>
            public static readonly DependencyProperty CommandParameterProperty =
                DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(CommandBehavior),
                    new FrameworkPropertyMetadata((object)null,
                        new PropertyChangedCallback(OnCommandParameterChanged)));
    
            /// <summary>
            /// Gets the CommandParameter property.  
            /// </summary>
            public static object GetCommandParameter(DependencyObject d)
            {
                return (object)d.GetValue(CommandParameterProperty);
            }
    
            /// <summary>
            /// Sets the CommandParameter property. 
            /// </summary>
            public static void SetCommandParameter(DependencyObject d, object value)
            {
                d.SetValue(CommandParameterProperty, value);
            }
    
            /// <summary>
            /// Handles changes to the CommandParameter property.
            /// </summary>
            private static void OnCommandParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                CommandBehaviorBinding binding = FetchOrCreateBinding(d);
                binding.CommandParameter = e.NewValue;
            }
    
            #endregion
    
            #region Event
    
            /// <summary>
            /// Event Attached Dependency Property
            /// </summary>
            public static readonly DependencyProperty EventProperty =
                DependencyProperty.RegisterAttached("Event", typeof(string), typeof(CommandBehavior),
                    new FrameworkPropertyMetadata((string)String.Empty,
                        new PropertyChangedCallback(OnEventChanged)));
    
            /// <summary>
            /// Gets the Event property.  This dependency property 
            /// indicates ....
            /// </summary>
            public static string GetEvent(DependencyObject d)
            {
                return (string)d.GetValue(EventProperty);
            }
    
            /// <summary>
            /// Sets the Event property.  This dependency property 
            /// indicates ....
            /// </summary>
            public static void SetEvent(DependencyObject d, string value)
            {
                d.SetValue(EventProperty, value);
            }
    
            /// <summary>
            /// Handles changes to the Event property.
            /// </summary>
            private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                CommandBehaviorBinding binding = FetchOrCreateBinding(d);
                //check if the Event is set. If yes we need to rebind the Command to the new event and unregister the old one
                if (binding.Event != null && binding.Owner != null)
                    binding.Dispose();
                //bind the new event to the command if newValue isn't blank (e.g. switching tabs)
                if (e.NewValue.ToString() != "")
                    binding.BindEvent(d, e.NewValue.ToString());
            }
    
            #endregion
    
            #region Helpers
            //tries to get a CommandBehaviorBinding from the element. Creates a new instance if there is not one attached
            private static CommandBehaviorBinding FetchOrCreateBinding(DependencyObject d)
            {
                CommandBehaviorBinding binding = CommandBehavior.GetBehavior(d);
                if (binding == null)
                {
                    binding = new CommandBehaviorBinding();
                    CommandBehavior.SetBehavior(d, binding);
                }
                return binding;
            }
            #endregion
    
        }
    
    }
    

    CommandBehaviorBinding.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Input;
    using System.Reflection;
    using System.Windows;
    
    // Code to attach a command to any event
    // From http://marlongrech.wordpress.com/2008/12/04/attachedcommandbehavior-aka-acb/
    namespace Keys.Controls.Helpers.CommandHelpers
    {
        /// <summary>
        /// Defines the command behavior binding
        /// </summary>
        public class CommandBehaviorBinding : IDisposable
        {
            #region Properties
            /// <summary>
            /// Get the owner of the CommandBinding ex: a Button
            /// This property can only be set from the BindEvent Method
            /// </summary>
            public DependencyObject Owner { get; private set; }
            /// <summary>
            /// The command to execute when the specified event is raised
            /// </summary>
            public ICommand Command { get; set; }
            /// <summary>
            /// Gets or sets a CommandParameter
            /// </summary>
            public object CommandParameter { get; set; }
            /// <summary>
            /// The event name to hook up to
            /// This property can only be set from the BindEvent Method
            /// </summary>
            public string EventName { get; private set; }
            /// <summary>
            /// The event info of the event
            /// </summary>
            public EventInfo Event { get; private set; }
            /// <summary>
            /// Gets the EventHandler for the binding with the event
            /// </summary>
            public Delegate EventHandler { get; private set; }
    
            #endregion
    
            //Creates an EventHandler on runtime and registers that handler to the Event specified
            public void BindEvent(DependencyObject owner, string eventName)
            {
                EventName = eventName;
                Owner = owner;
                Event = Owner.GetType().GetEvent(EventName, BindingFlags.Public | BindingFlags.Instance);
                if (Event == null)
                    throw new InvalidOperationException(String.Format("Could not resolve event name {0}", EventName));
    
                //Create an event handler for the event that will call the ExecuteCommand method
                EventHandler = EventHandlerGenerator.CreateDelegate(
                    Event.EventHandlerType, typeof(CommandBehaviorBinding).GetMethod("ExecuteCommand", BindingFlags.Public | BindingFlags.Instance), this);
                //Register the handler to the Event
                Event.AddEventHandler(Owner, EventHandler);
            }
    
            /// <summary>
            /// Executes the command
            /// </summary>
            public void ExecuteCommand()
            {
                if (Command != null && Command.CanExecute(CommandParameter))
                    Command.Execute(CommandParameter);
            }
    
            #region IDisposable Members
            bool disposed = false;
            /// <summary>
            /// Unregisters the EventHandler from the Event
            /// </summary>
            public void Dispose()
            {
                if (!disposed)
                {
                    Event.RemoveEventHandler(Owner, EventHandler);
                    disposed = true;
                }
            }
    
            #endregion
        }
    }
    

    EventHandlerGenerator.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Reflection.Emit;
    using System.Reflection;
    
    // Code to attach a command to any event
    // From http://marlongrech.wordpress.com/2008/12/04/attachedcommandbehavior-aka-acb/
    namespace Keys.Controls.Helpers.CommandHelpers
    {
        /// <summary>
        /// Generates delegates according to the specified signature on runtime
        /// </summary>
        public static class EventHandlerGenerator
        {
            /// <summary>
            /// Generates a delegate with a matching signature of the supplied eventHandlerType
            /// This method only supports Events that have a delegate of type void
            /// </summary>
            /// <param name="eventInfo">The delegate type to wrap. Note that this must always be a void delegate</param>
            /// <param name="methodToInvoke">The method to invoke</param>
            /// <param name="methodInvoker">The object where the method resides</param>
            /// <returns>Returns a delegate with the same signature as eventHandlerType that calls the methodToInvoke inside</returns>
            public static Delegate CreateDelegate(Type eventHandlerType, MethodInfo methodToInvoke, object methodInvoker)
            {
                //Get the eventHandlerType signature
                var eventHandlerInfo = eventHandlerType.GetMethod("Invoke");
                Type returnType = eventHandlerInfo.ReturnParameter.ParameterType;
                if (returnType != typeof(void))
                    throw new ApplicationException("Delegate has a return type. This only supprts event handlers that are void");
    
                ParameterInfo[] delegateParameters = eventHandlerInfo.GetParameters();
                //Get the list of type of parameters. Please note that we do + 1 because we have to push the object where the method resides i.e methodInvoker parameter
                Type[] hookupParameters = new Type[delegateParameters.Length + 1];
                hookupParameters[0] = methodInvoker.GetType();
                for (int i = 0; i < delegateParameters.Length; i++)
                    hookupParameters[i + 1] = delegateParameters[i].ParameterType;
    
                DynamicMethod handler = new DynamicMethod("", null,
                    hookupParameters, typeof(EventHandlerGenerator));
    
                ILGenerator eventIL = handler.GetILGenerator();
    
                //load the parameters or everything will just BAM :)
                LocalBuilder local = eventIL.DeclareLocal(typeof(object[]));
                eventIL.Emit(OpCodes.Ldc_I4, delegateParameters.Length + 1);
                eventIL.Emit(OpCodes.Newarr, typeof(object));
                eventIL.Emit(OpCodes.Stloc, local);
    
                //start from 1 because the first item is the instance. Load up all the arguments
                for (int i = 1; i < delegateParameters.Length + 1; i++)
                {
                    eventIL.Emit(OpCodes.Ldloc, local);
                    eventIL.Emit(OpCodes.Ldc_I4, i);
                    eventIL.Emit(OpCodes.Ldarg, i);
                    eventIL.Emit(OpCodes.Stelem_Ref);
                }
    
                eventIL.Emit(OpCodes.Ldloc, local);
    
                //Load as first argument the instance of the object for the methodToInvoke i.e methodInvoker
                eventIL.Emit(OpCodes.Ldarg_0);
    
                //Now that we have it all set up call the actual method that we want to call for the binding
                eventIL.EmitCall(OpCodes.Call, methodToInvoke, null);
    
                eventIL.Emit(OpCodes.Pop);
                eventIL.Emit(OpCodes.Ret);
    
                //create a delegate from the dynamic method
                return handler.CreateDelegate(eventHandlerType, methodInvoker);
            }
    
        }
    }
    

    【讨论】:

    • 我试图下载该文件,但可惜我在工作中被阻止...我会从家里得到它,明天告诉你我的想法。谢谢。
    • 感谢 Rachel,经过谷歌搜索和更多研究 (Josh Smith),我决定处理此问题的最佳方法实际上是按照 Mark 的建议公开单独的属性。
    • @John 我同意,我总是更喜欢在 ViewModel 而不是 View 中执行我的代码 :)
    猜你喜欢
    • 2023-03-09
    • 1970-01-01
    • 2013-03-12
    • 1970-01-01
    • 1970-01-01
    • 2022-08-19
    • 1970-01-01
    • 1970-01-01
    • 2019-04-07
    相关资源
    最近更新 更多