web-dev-qa-db-ja.com

WPF:MVVMを使用してコマンドをListBoxItemにバインドする方法

MVVMの学習を始めました。私はこの MVVMチュートリアル (そこにいるすべてのMVVM初心者に強くお勧めします)に従ってアプリケーションをゼロから作成しました。基本的に、これまでに作成したのは、ユーザーが自分のデータを追加するテキストボックスと、そのデータを保存するボタンです。

ここで私は立ち往生しました:ListBoxItemをダブルクリックし、作成してViewModelに追加したコマンドをトリガーできるようにしたいのです。 XAML側を終了する方法がわかりません。つまり、そのコマンドをListBox(Item)にバインドする方法がわかりません。

XAMLは次のとおりです。

...
<ListBox 
    Name="EntriesListBox" 
    Width="228" 
    Height="208" 
    Margin="138,12,0,0" 
    HorizontalAlignment="Left" 
    VerticalAlignment="Top" 
    ItemsSource="{Binding Entries}" />
...

ViewModelは次のとおりです。

public class MainWindowViewModel : DependencyObject
{
    ...
    public IEntriesProvider Entries
    {
        get { return entries; }
    }

    private IEntriesProvider entries;
    public OpenEntryCommand OpenEntryCmd { get; set; }

    public MainWindowViewModel(IEntriesProvider source)
    {
        this.entries = source;
        ...
        this.OpenEntryCmd = new OpenEntryCommand(this);
    }
    ...
}

最後に、ユーザーがEntriesListBoxのアイテムをダブルクリックしたときに実行するOpenEntryCommandを次に示します。

public class OpenEntryCommand : ICommand
{
    private MainWindowViewModel viewModel;

    public OpenEntryCommand(MainWindowViewModel viewModel)
    {
        this.viewModel = viewModel;
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public bool CanExecute(object parameter)
    {
        return parameter is Entry;
    }

    public void Execute(object parameter)
    {
        string messageFormat = "Subject: {0}\nStart: {1}\nEnd: {2}";
        Entry entry = parameter as Entry;
        string message = string.Format(messageFormat, 
                                       entry.Subject, 
                                       entry.StartDate.ToShortDateString(), 
                                       entry.EndDate.ToShortDateString());

        MessageBox.Show(message, "Appointment");
    }
}

助けてください、私はそれを感謝します。

26
Boris

残念ながら、ButtonBase派生コントロールのみがICommandオブジェクトをそれらのCommandプロパティにバインドする可能性があります(Clickイベントの場合)。

ただし、Blendが提供するAPIを使用して、イベント(MouseDoubleClick上のListBoxなど)をICommandオブジェクトにマッピングできます。

<ListBox>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseDoubleClick">
            <i:InvokeCommandAction Command="{Binding YourCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ListBox>

以下を定義する必要があります:xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"およびSystem.Windows.Interactivity.dllへの参照があります。

-編集-これはWPF4の一部ですが、WPF4を使用していない場合は、Microsoft.Windows.Interactivityを使用できます。このdllはBlend SDKからのもので、Blendを必要としません。ここから http://www.Microsoft.com/downloads/en/details.aspx?FamilyID=f1ae9a30-4928-411d-970b-e682ab179e17&displaylang = en

更新:役に立つ何かを見つけました。 MVVM Light Toolkitのこのリンク を確認してください。これには、これを行う方法のチュートリアルと、必要なライブラリへの リンク が含まれています。 MVVM Light Toolkitは、MVVMをSilverlight、WPF、およびWP7に適用するための非常に興味深いフレームワークです。

お役に立てれば :)

64
AbdouMoumen

これは、DoubleClickイベントのために注意が必要です。これを行うにはいくつかの方法があります。

  1. コードビハインドでダブルクリックイベントを処理し、ViewModelでコマンド/メソッドを手動で呼び出します
  2. 添付の動作を使用して、 DoubleClickイベントをコマンドにルーティング
  3. Blend Behaviorを使用して DoubleClickイベントをコマンドにマッピングします

2と3の方が純粋かもしれませんが、率直に言って、1は簡単で複雑ではなく、世界で最悪のものではありません。 1回限りのケースでは、おそらくアプローチ1を使用します。

ここで、たとえば各アイテムにハイパーリンクを使用するように要件を変更した場合は、より簡単になります。 XAMLでルート要素に名前を付けることから始めます-たとえば、Windowの場合:

<Window .... Name="This">

次に、ListBoxアイテムのDataTemplateで、次のようなものを使用します。

<ListBox ...>
  <ListBox.ItemTemplate>
    <DataTemplate>
      <Hyperlink 
        Command="{Binding ElementName=This, Path=DataContext.OpenEntryCmd}"
        Text="{Binding Path=Name}" 
        />

ElementNameバインディングを使用すると、特定のデータ項目ではなく、ViewModelのコンテキストからOpenEntryCmdを解決できます。

7
Paul Stovell

これを行う最善の方法は、コマンドとパラメーターの依存関係プロパティを使用して、コンテンツの単純なユーザーコントロールラッパーを作成することです。

これを行った理由は、ButtonがクリックイベントをListBoxにバブリングせず、ListBoxItemを選択できなかったためです。

CommandControl.xaml.cs:

public partial class CommandControl : UserControl
{
    public CommandControl()
    {
        MouseLeftButtonDown += OnMouseLeftButtonDown;
        InitializeComponent();
    }

    private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
    {
        if (Command != null)
        {
            if (Command.CanExecute(CommandParameter))
            {
                Command.Execute(CommandParameter);
            }
        }
    }

    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register("Command", typeof(ICommand),
            typeof(CommandControl),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    public static readonly DependencyProperty CommandParameterProperty =
        DependencyProperty.Register("CommandParameter", typeof(object),
            typeof(CommandControl),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));

    public object CommandParameter
    {
        get { return (object)GetValue(CommandParameterProperty); }
        set { SetValue(CommandParameterProperty, value); }
    }
}

CommandControl.xaml:

<UserControl x:Class="WpfApp.UserControls.CommandControl"
         xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.Microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300"
         Background="Transparent">
</UserControl>

使用法:

<ListBoxItem>
    <uc:CommandControl Command="{Binding LoadPageCommand}"
                       CommandParameter="{Binding HomePageViewModel}">
        <TextBlock Text="Home" Margin="0,0,0,5" VerticalAlignment="Center"
                   Foreground="White" FontSize="24" />
    </uc:CommandControl>
</ListBoxItem>

コンテンツは何でもかまいません。コントロールをクリックすると、コマンドが実行されます。

編集:追加Background="Transparent"をUserControlに追加して、コントロールの領域全体でクリックイベントを有効にします。

2
Shahin Dohan

これはちょっとしたハックですが、うまく機能し、コマンドを使用してコードビハインドを回避できます。これには、ScrollViewがコンテナ全体を満たさないと仮定して、空のListBoxItemsエリアでダブルクリック(またはトリガーが何であれ)してもコマンドをトリガーしないという利点もあります。

基本的に、DataTemplateで構成されるListBoxTextBlockを作成し、TextBlockの幅をListBox、マージンとパディングを0に設定し、水平スクロールを無効にします(TextBlockScrollViewの表示範囲を超えて出血するため、水平スクロールバーがトリガーされます)。私が見つけた唯一のバグは、ユーザーがListBoxItemの境界線を正確にクリックしてもコマンドが実行されないことです。

以下に例を示します。

<ListBox
    x:Name="listBox"
    Width="400"
    Height="150"
    ScrollViewer.HorizontalScrollBarVisibility="Hidden"
    ItemsSource="{Binding ItemsSourceProperty}"
    SelectedItem="{Binding SelectedItemProperty}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Padding="0" 
                        Margin="0" 
                        Text="{Binding DisplayTextProperty}" 
                        Width="{Binding ElementName=listBox, Path=Width}">
                <TextBlock.InputBindings>
                    <MouseBinding 
                        Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.SelectProjectCommand}" 
                                    Gesture="LeftDoubleClick" />
                </TextBlock.InputBindings>
            </TextBlock>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
2
manic_coder