简体   繁体   English

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

[英]How to hookup TextBox's TextChanged event and Command in order to use MVVM pattern in Silverlight

Recently, I realized that MVVM pattern is so useful for Silverlight application and studying how to adopt it into my project. 最近,我意识到MVVM模式对Silverlight应用程序非常有用,并研究如何将它应用到我的项目中。

BTW, how to hook up the textbox's textChanged event with Command? 顺便说一句,如何用Command连接文本框的textChanged事件? There is Command property for Button, however Textbox has no commapd property. Button有Command属性,但Textbox没有commapd属性。 If Controls don't have command property, how to combine ICommand and Control's event? 如果Controls没有命令属性,如何组合ICommand和Control的事件?

I got following xaml code 我得到了以下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>

What I am trying to do is that if user input some text in the textbox, data will be shown in the datagrid instead of using button click. 我想要做的是,如果用户在文本框中输入一些文本,数据将显示在数据网格中而不是使用按钮单击。 I know there is autocomplete box control built in. however, I want to know how to call Command property in the ViewModel class in the controls which does not have Command property such as textbox. 我知道内置了自动完成框控件。但是,我想知道如何在没有Command属性的控件中调用ViewModel类中的Command属性,例如textbox。

Thanks 谢谢

Why not just bind the Text property to a property on your view model? 为什么不将Text属性绑定到视图模型上的属性? That way you get notified when it has changed, and also get the new value: 这样,您可以在更改时收到通知,并获得新值:

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

XAML: XAML:

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

Here's the easiest way. 这是最简单的方法。 Bind your text box to the property on the view model, as you described above. 如上所述,将文本框绑定到视图模型上的属性。 Then, simply add a code-behind (yes, I'm talking code-behind with MVVM, it's not the end of the world) event to the Text Box. 然后,只需在文本框中添加一个代码隐藏(是的,我正在与MVVM进行代码隐藏,它不是世界末日)事件。 Add a TextChanged event, then simply refresh the binding. 添加TextChanged事件,然后只需刷新绑定。

Altogether, you'll have something like this for a view model: 总而言之,对于视图模型,您将拥有类似的内容:

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

In your XAML, you'll have this: 在你的XAML中,你将拥有:

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

Finally, in the code behind, simply do this: 最后,在后面的代码中,只需执行以下操作:

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

That will cause your property to update anytime the text changes. 这将导致您的属性在文本更改时随时更新。 } }

Here's the MvvmLight way of doing it! 这是MvvmLight的做法! Credit goes to GalaSoft Laurent Bugnion. 归功于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>

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

In the definition section we add: 在定义部分,我们添加:

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

If you use the TextBox , add the reference to the event we want to detect: 如果使用TextBox ,请添加对我们要检测的事件的引用:

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

In the ViewModel add code for Commad: 在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
}

Do not forget to perform the binding text: 不要忘记执行绑定文本:

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

简单地使用

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

For the sake of conversation, lets say that you do need to hook up some arbitrary event to a Command rather than bind directly to a property on the ViewModel (due to a lack of support in the control or framework, defect, etc.) This can be done in the codebehind. 为了对话,让我们说你确实需要将一些任意事件连接到Command而不是直接绑定到ViewModel上的属性(由于控件或框架中缺少支持,缺陷等)可以在代码隐藏中完成。 Contrary to some misconceptions, MVVM does not preclude codebehind. 与一些误解相反,MVVM并不排除代码隐藏。 It is just important to remember that the logic in the codebehind should not cross layers - it should be relate directly to the UI and the specific UI technology being used. 重要的是要记住,代码隐藏中的逻辑不应跨越层 - 它应该直接与UI和所使用的特定UI技术相关。 (Note however, that putting 95% of your work in the markup file may make it a little unintutitive to have some functionality in the codebehind, so a comment or two in the markup about this one-off codebehind implementation may make things easier down the road for yourself or others.) (但请注意,将95%的工作放在标记文件中可能会使代码隐藏中的某些功能变得有点不明确,因此关于这种一次性代码隐藏实现的标记中的一两条注释可能会使事情变得更容易为自己或他人的道路。)

There are usually 2 parts to binding a command in codebehind. 在代码隐藏中绑定命令通常有两个部分。 First, you have to respond to the event. 首先,你必须回应这个事件。 Second, you (may) want to tie into the command's CanExecute property. 其次,您(可能)想要绑定命令的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));
    }
}

I solved it by binding to a property on my view model and setting the binding's UpdateSourceTrigger to PropertyChanged. 我通过绑定到我的视图模型上的属性并将绑定的UpdateSourceTrigger设置为PropertyChanged来解决它。 The property supports INotifyPropertyChanged. 该属性支持INotifyPropertyChanged。

In my view model I then subscribe to the PropertyChanged event for the property. 在我的视图模型中,然后我订阅了属性的PropertyChanged事件。 When it triggers I perform the tasks I need to do (in my case updating a collection) and at the end I call PropertyChanged on the property that my other stuff in the view is listening to. 当它触发时,我执行我需要做的任务(在我的情况下更新集合),最后我在属性上调用PropertyChanged,我在视图中的其他东西正在监听。

You should use a Behavior to execute the Command: 您应该使用行为来执行命令:

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

Now you can "Bind" your command in xaml: 现在你可以在xaml中“绑定”你的命令了:

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

If you need you can extend this Behavior with extra properties, for example if you need the sender or the DataContext of a different element.. 如果需要,可以使用额外的属性扩展此行为,例如,如果您需要其他元素的发送者或DataContext。

Kind regards, Tamás 亲切的问候,Tamás

(I have found this on a blog post, but I can not remember for it's address) (我在博客文章中发现了这个,但我不记得它的地址)

Jeremy answered it. 杰里米回答说。 However, if you want to reduce code behind just do something like this. 但是,如果你想减少代码背后只是做这样的事情。 In your view model: 在您的视图模型中:

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

Then in code behind: 然后在代码后面:

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

I know it's duplicated code, but if this is what you want, then here it is. 我知道它是重复的代码,但如果这是你想要的,那么就是这样。

I had the same question. 我有同样的问题。 Then I find this article. 然后我找到这篇文章。

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

  1. Create behavior ( behavior recive value to changend event and command to changend) 创建behaviorbehavior recive值以更改事件和命令以更改)
  2. Implement behavior in your WPF 在WPF中实现behavior
  3. Bind Behavior your command to a ValueChanged 绑定Behavior的命令到ValueChanged

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM