最近、MVVMパターンがSilverlightアプリケーションに非常に有用であることに気付き、それをプロジェクトに採用する方法を研究しました。
ところで、テキストボックスのtextChangedイベントをコマンドでフックする方法は? ButtonにはCommandプロパティがありますが、Textboxにはcompampdプロパティがありません。コントロールにコマンドプロパティがない場合、ICommandとコントロールのイベントを組み合わせる方法は?
私は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プロパティを呼び出す方法を知りたいです。
ありがとう
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}"/>
これが最も簡単な方法です。上で説明したように、テキストボックスをビューモデルのプロパティにバインドします。次に、コードビハインド(そう、私は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();
}
これにより、テキストが変更されるたびにプロパティが更新されます。 }
これが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>
定義セクションに追加します:
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));
}
}
単に使う
<TextBox Text="{Binding MyText,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
会話のために、ViewModelのプロパティに直接バインドするのではなく、コマンドに任意のイベントをフックする必要があるとしましょう(コントロールまたはフレームワークでのサポートの欠如、不具合など)。分離コードで実行できます。誤解に反して、MVVMは分離コードを排除しません。コードビハインドのロジックはレイヤーをまたがってはいけないことを覚えておくことが重要です。UIと使用されている特定のUIテクノロジーに直接関連している必要があります。 (ただし、作業の95%をマークアップファイルに入れると、コードビハインドにいくつかの機能があると直感的に理解できなくなる可能性があるため、この1回限りのコードビハインド実装に関するマークアップのコメントが1つまたは2つになる可能性があります。自分や他の人にとって、物事が簡単に進むことができます。)
通常、コードビハインドでコマンドをバインドするには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));
}
}
ビューモデルのプロパティにバインドし、バインディングのUpdateSourceTriggerをPropertyChangedに設定することで解決しました。プロパティはINotifyPropertyChangedをサポートします。
次に、ビューモデルで、プロパティのPropertyChangedイベントをサブスクライブします。それがトリガーされると、実行する必要があるタスクを実行し(私の場合はコレクションを更新します)、最後に、ビュー内の他のコンテンツがリッスンしているプロパティでPropertyChangedを呼び出します。
コマンドを実行するには、動作を使用する必要があります。
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が必要な場合などです。
よろしく、タマス
(私はこれをブログの投稿で見つけましたが、そのアドレスを思い出せません)
ジェレミーはそれに答えました。ただし、背後のコードを削減したい場合は、次のようにします。ビューモデルで:
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);
}
重複したコードであることはわかっていますが、これが必要な場合は、ここにあります。