【问题标题】:How to hookup TextBox's TextChanged event and Command in order to use MVVM pattern in Silverlight如何连接 TextBox 的 TextChanged 事件和命令以便在 Silverlight 中使用 MVVM 模式
【发布时间】:2011-03-30 07:45:09
【问题描述】:

最近,我意识到 MVVM 模式对于 Silverlight 应用程序非常有用,并正在研究如何将它应用到我的项目中。

顺便说一句,如何用命令连接文本框的 textChanged 事件? Button 有 Command 属性,但 Textbox 没有 commapd 属性。 如果Controls没有command属性,如何结合ICommand和Control的事件?

我得到了以下 xaml 代码

<UserControl.Resources>
        <vm:CustomerViewModel x:Key="customerVM"/>    
    </UserControl.Resources>

    <Grid x:Name="LayoutRoot" 
          Background="White" 
          DataContext="{Binding Path=Customers, Source={StaticResource customerVM}, Mode=TwoWay}" >

        <StackPanel>
            <StackPanel Orientation="Horizontal"
                        Width="300"
                        HorizontalAlignment="Center">
                <TextBox x:Name="tbName" 
                         Width="50" 
                         Margin="10"/>
                <Button Width="30" 
                        Margin="10" 
                        Content="Find"
                        Command="{Binding Path=GetCustomersByNameCommand, Source={StaticResource customerVM}}"
                        CommandParameter="{Binding Path=Text, ElementName=tbName}"/>
            </StackPanel>
            <sdk:DataGrid ItemsSource="{Binding Path=DataContext, ElementName=LayoutRoot}"
                          AutoGenerateColumns="True"
                          Width="300"
                          Height="300"/>
        </StackPanel>
    </Grid>

我想要做的是,如果用户在文本框中输入一些文本,数据将显示在数据网格中,而不是使用按钮单击。 我知道内置了自动完成框控件。但是,我想知道如何在没有 Command 属性的控件中调用 ViewModel 类中的 Command 属性,例如文本框。

谢谢

【问题讨论】:

  • 在研究了几个小时来解决这个问题后,我发现了 MvvmLightToolkit(mvvmlight.codeplex.com) 它使得 MVVM 的使用变得很容易。再次感谢大家。
  • 这个 link 是 UpdateSourceTrigger=PropertyChanged 的​​更好解决方案

标签: xaml silverlight mvvm


【解决方案1】:

为什么不将Text 属性绑定到视图模型上的属性?这样,您会在更改时收到通知,并获得新值:

public string MyData
{
    get { return this.myData; }
    set
    {
        if (this.myData != value)
        {
            this.myData = value;
            this.OnPropertyChanged(() => this.MyData);
        }
    }
}

XAML:

<TextBox Text="{Binding MyData}"/>

【讨论】:

  • @kwon,这个答案仍然有效。您只需要在绑定中设置UpdateSourceTrigger=PropertyChanged,每次用户在文本框中键入字符时,VM 属性都会更新。所以你只需要在属性设置器中做任何你需要做的事情
  • @Thomas,UpdateSourcetrigger 是 WPF,在 Silverlight 中不起作用。
  • 呃,不知道...我不敢相信 Silverlight 中缺少的功能(包括主要功能)的数量
  • 您忘了提到您必须失去焦点才能触发 text 属性的属性更改事件。
  • UpdateSourceTrigger=PropertyChanged 在 SL5 绑定中可用
【解决方案2】:

这是最简单的方法。如上所述,将您的文本框绑定到视图模型上的属性。然后,只需向文本框添加一个代码隐藏(是的,我说的是 MVVM 的代码隐藏,这不是世界末日)事件。添加一个 TextChanged 事件,然后只需刷新绑定。

总而言之,对于视图模型,您将拥有这样的东西:

public class MyViewModel 
{
    private string _myText;

    public string MyText 
    {
        get { return _myText; }
        set 
        {
            _myText = value;
            RaisePropertyChanged("MyText"); // this needs to be implemented
            // now do whatever grid refresh/etc
        }
    }
}

在您的 XAML 中,您将拥有以下内容:

<TextBox Text="{Binding MyText,Mode=TwoWay}" TextChanged="TextBox_TextChanged"/>

最后,在后面的代码中,只需这样做:

public void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
   var binding = ((TextBox)sender).GetBindingExpression(TextBox.TextProperty);
   binding.UpdateSource();
}

这将导致您的属性在文本更改时更新。 }

【讨论】:

  • 感谢 Jeremy,这是解决我的问题的方法,但是,我正在尝试减少代码隐藏中的代码。没有代码隐藏就没有办法吗?
  • 问题是……为什么?你为什么要尝试减少代码隐藏。在后面的代码中包含事件或提出您在 XAML 中指定的附加行为之间有什么区别?您仍然有代码隐藏,在一种情况下它是显式的(行为),另一种是隐式的(代码隐藏文件)。您可以使用触发器解决此问题,但我只是不确定您为什么要做额外的工作。
  • 我必须同意 Jeremy 的观点,MVVM 并不是要减少或消除代码隐藏,而是要分离关注点和可测试性。将 UI 代码放在代码隐藏中非常有意义,因为它是 UI 代码,通常不是您将要测试的东西。
  • 我试过了,效果很好。但是,我也尝试使用 UpdateSourceTrigger=PropertyChanged (如其他 cmets/answers 中所述),而不是后面的 TextChanged 代码并且它有效!这是 Silverlight 5 中添加的一项功能,因此您现在可以摆脱代码隐藏 :-)
【解决方案3】:

在定义部分我们添加:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

如果您使用TextBox,请添加对我们要检测的事件的引用:

<TextBox Text="{Binding TextPrintersFilter}">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="TextChanged">          
      <i:InvokeCommandAction Command="{Binding FilterTextChangedCommand}"/>
    </i:EventTrigger>
  </i:Interaction.Triggers>
</TextBox>

在 ViewModel 中为 Commad 添加代码:

public ICommand FilterTextChangedCommand
{
  get
  {
    if (this._filterTextChangedCommand == null)
    {
      this._filterTextChangedCommand =
        new RelayCommand(param => this.OnRequestFilterTextChanged());
    }
    return this._filterTextChangedCommand;
  }
}

private void OnRequestFilterTextChanged()
{
  // Add code
}

不要忘记执行绑定文本:

private string _textPrintersFilter;
public string TextPrintersFilter
{
  get { return _textPrintersFilter; }
  set
  {
    _textPrintersFilter = value;
    this.RaisePropertyChange(nameof(TextPrintersFilter));
  }
}

【讨论】:

  • 你的 InvokeCommandAction 标签中不应该有一个 CommandParameter 吗?否则你只会得到 null 。
  • FilterTextCommand 正是我需要的地方。我不想污染我的模型
【解决方案4】:

这是 MvvmLight 的做法!归功于 GalaSoft Laurent Bugnion。

<sdk:DataGrid Name="dataGrid1" Grid.Row="1"
    ItemsSource="{Binding Path=CollectionView}"
    IsEnabled="{Binding Path=CanLoad}"
    IsReadOnly="True">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <cmd:EventToCommand
                Command="{Binding SelectionChangedCommand}"
                CommandParameter="{Binding SelectedItems, ElementName=dataGrid1}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</sdk:DataGrid>

来源: http://blog.galasoft.ch/archive/2010/05/19/handling-datagrid.selecteditems-in-an-mvvm-friendly-manner.aspx

【讨论】:

  • 请注意,在 XAML 中正确导入 i 命名空间是 xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
  • TextBox 不是选中项。你会使用什么事件? TextChanged 似乎在这里不起作用。
  • 请问 cmd 命名空间的导入是什么?该博客现已过时,搜索(到目前为止)一无所获。
  • 编辑:这是对我有用的命名空间:xmlns:cmd="galasoft.ch/mvvmlight"
【解决方案5】:

简单地使用

<TextBox Text="{Binding MyText,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

【讨论】:

    【解决方案6】:

    为了便于交流,假设您确实需要将一些任意事件连接到命令,而不是直接绑定到 ViewModel 上的属性(由于缺乏对控件或框架的支持、缺陷等.) 这可以在代码隐藏中完成。与一些误解相反,MVVM 并不排除代码隐藏。重要的是要记住,代码隐藏中的逻辑不应该跨层 - 它应该直接与 UI 和正在使用的特定 UI 技术相关。 (但是请注意,将 95% 的工作放在标记文件中可能会使代码隐藏中的某些功能变得有点不直观,因此标记中关于这种一次性代码隐藏实现的一两条评论可能会有所作为对自己或他人来说更轻松。)

    在代码隐藏中绑定命令通常有 2 个部分。首先,您必须响应事件。其次,您(可能)想要绑定到命令的 CanExecute 属性。

    // Execute the command from the codebehind
    private void HandleTheEvent(Object sender, EventArgs e)
    {
        var viewModel = DataContext as ViewModel;
        if (viewModel != null)
        {
            var command = viewModel.SomeCommand;
            command.Execute(null);
        }
    }
    
    // Listen for the command's CanExecuteChanged event
    // Remember to call this (and unhook events as well) whenever the ViewModel instance changes
    private void ListenToCommandEvent()
    {
        var viewModel = DataContext as ViewModel;
        if (viewModel != null)
        {
            var command = viewModel.SomeCommand;
            command.CanExecuteChanged += (o, e) => EnableOrDisableControl(command.CanExecute(null));
        }
    }
    

    【讨论】:

      【解决方案7】:

      您应该使用行为来执行命令:

      public class CommandBehavior : TriggerAction<FrameworkElement>
      {
          public static readonly DependencyProperty CommandBindingProperty = DependencyProperty.Register(
              "CommandBinding",
              typeof(string),
              typeof(CommandBehavior),
              null);
      
          public string CommandBinding
          {
              get { return (string)GetValue(CommandBindingProperty); }
              set { SetValue(CommandBindingProperty, value); }
          }
      
          private ICommand _action;
      
          protected override void OnAttached()
          {
              DataContextChangedHandler.Bind(AssociatedObject, _ProcessCommand);
          }
      
          private void _ProcessCommand(FrameworkElement obj)
          {
              if (AssociatedObject != null)
              {
      
                  var dataContext = AssociatedObject.DataContext;
      
                  if (dataContext != null)
                  {
                      var property = dataContext.GetType().GetProperty(CommandBinding);
                      if (property != null)
                      {
                          var value = property.GetValue(dataContext, null);
                          if (value != null && value is ICommand)
                          {
                              _action = value as ICommand;
                              if (AssociatedObject is Control)
                              {
                                  var associatedControl = AssociatedObject as Control;
                                  associatedControl.IsEnabled = _action.CanExecute(null);
                                  _action.CanExecuteChanged +=
                                      (o, e) => associatedControl.IsEnabled = _action.CanExecute(null);
                              }
      
                          }
                      }
                  }
              }
          }
      
          protected override void Invoke(object parameter)
          {
              if (_action != null && _action.CanExecute(parameter))
              {
                  _action.Execute(parameter);
              }
          }
      }
      
      public static class DataContextChangedHandler
      {
          private const string INTERNAL_CONTEXT = "InternalDataContext";
          private const string CONTEXT_CHANGED = "DataContextChanged";
      
          public static readonly DependencyProperty InternalDataContextProperty =
              DependencyProperty.Register(INTERNAL_CONTEXT,
                                          typeof(Object),
                                          typeof(FrameworkElement),
                                          new PropertyMetadata(_DataContextChanged));
      
          public static readonly DependencyProperty DataContextChangedProperty =
              DependencyProperty.Register(CONTEXT_CHANGED,
                                          typeof(Action<FrameworkElement>),
                                          typeof(FrameworkElement),
                                          null);
      
      
          private static void _DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
          {
              var control = (FrameworkElement)sender;
              var handler = (Action<FrameworkElement>)control.GetValue(DataContextChangedProperty);
              if (handler != null)
              {
                  handler(control);
              }
          }
      
          public static void Bind(FrameworkElement control, Action<FrameworkElement> dataContextChanged)
          {
              control.SetBinding(InternalDataContextProperty, new Binding());
              control.SetValue(DataContextChangedProperty, dataContextChanged);
          }
      }
      

      现在您可以在 xaml 中“绑定”您的命令:

              <TextBox Text="{Binding SearchText, Mode=TwoWay}" >
                  <i:Interaction.Triggers>
                      <i:EventTrigger EventName="TextChanged">
                          <utils:CommandBehavior CommandBinding="SearchCommand" />
                      </i:EventTrigger>
                  </i:Interaction.Triggers>
              </TextBox>
      

      如果您需要,您可以使用额外的属性扩展此行为,例如,如果您需要不同元素的发送者或 DataContext..

      亲切的问候, 塔马斯

      (我在一篇博客文章中找到了这个,但我不记得它的地址)

      【讨论】:

      • 感谢您的回复 做一些非常简单的事情就是这么复杂的代码。我很失望银光。无论如何,是否有任何框架可以减少复杂代码并解决 MVVM 的此类问题?
      【解决方案8】:

      我通过绑定到我的视图模型上的属性并将绑定的 UpdateSourceTrigger 设置为 PropertyChanged 解决了这个问题。该属性支持 INotifyPropertyChanged。

      然后在我的视图模型中订阅该属性的 PropertyChanged 事件。当它触发时,我执行我需要执行的任务(在我的情况下更新集合),最后我在视图中的其他内容正在侦听的属性上调用 PropertyChanged。

      【讨论】:

        【解决方案9】:

        杰里米回答了。但是,如果您想减少后面的代码,请执行以下操作。在您的视图模型中:

        public class MyViewModel 
        {
            private string _myText;
        
            public string MyText 
            {
                get { return _myText; }
                set 
                {
                    _myText = value;
                    RaisePropertyChanged("MyText"); // this needs to be implemented
                    // now do whatever grid refresh/etc
                }
            }
            public void TextBox_TextChanged(object sender, TextChangedEventArgs e)
            {
                var binding = ((TextBox)sender).GetBindingExpression(TextBox.TextProperty);
                binding.UpdateSource();
            }
        }
        

        然后在后面的代码中:

        public void TextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            YourViewModel.TextBox_TextChanged(sender, e);
        }
        

        我知道这是重复的代码,但如果这是你想要的,那就在这里。

        【讨论】:

          【解决方案10】:

          我也有同样的问题。然后我找到了这篇文章。

          http://deanchalk.com/wpf-mvvm-property-changed-command-behavior/

          1. 创建behaviorbehavior接收值到changend事件和命令到changend)
          2. 在您的 WPF 中实现 behavior
          3. Behavior 你的命令绑定到ValueChanged

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-02-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多