如何连接TextBox的TextChanged事件和Command以便在Silverlight中使用MVVM模式

时间:2010-08-16 08:07:46

标签: xaml silverlight mvvm

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

BTW,如何使用Command挂接文本框的textChanged事件? Button有Command属性,但Textbox没有commapd属性。 如果控件没有命令属性,如何组合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属性,如textbox。

谢谢

10 个答案:

答案 0 :(得分:22)

为什么不将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}"/>

答案 1 :(得分:20)

这是最简单的方法。如上所述,将文本框绑定到视图模型上的属性。然后,只需在文本框中添加一个代码隐藏(是的,我正在与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();
}

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

答案 2 :(得分:9)

这是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

答案 3 :(得分:4)

只需使用

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

答案 4 :(得分:4)

在定义部分,我们添加:

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));
  }
}

答案 5 :(得分:3)

为了对话,我们可以说你需要将一些任意事件连接到Command而不是直接绑定到ViewModel上的属性(由于控件或框架中缺少支持,缺陷等) 。)这可以在代码隐藏中完成。与一些误解相反,MVVM并不排除代码隐藏。重要的是要记住,代码隐藏中的逻辑不应跨越层 - 它应该直接与UI和所使用的特定UI技术相关。 (但是请注意,将95%的工作放在标记文件中可能会使代码隐藏中的某些功能变得有点不明确,因此关于此一次性代码隐藏实现的标记中的注释或两个可能会产生一些影响为自己或他人提供更轻松的便利。)

在代码隐藏中绑定命令通常有两个部分。首先,你必须回应这个事件。其次,您(可能)想要绑定命令的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));
    }
}

答案 6 :(得分:0)

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

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。

亲切的问候, 陶

(我在博客文章中发现了这个,但我不记得它的地址)

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

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

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);
}

我知道它是重复的代码,但是如果您要的是这里,就在这里。

答案 9 :(得分:-1)

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

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

  1. 创建behaviorbehavior recive value to changend event and command to changend)
  2. 在WPF中实施behavior
  3. Behavior命令绑定到ValueChanged