回答:
コードビハインドは悪いことではありません。残念ながら、WPFコミュニティの多くの人がこれを間違っています。
MVVMは、背後にあるコードを排除するためのパターンではありません。それは、ロジックパーツ(ワークフロー)からビューパーツ(外観、アニメーションなど)を分離することです。さらに、ロジック部分を単体テストすることができます。
データバインディングがすべての解決策ではないため、コードを背後で記述しなければならないシナリオは十分にわかっています。あなたのシナリオでは、コードビハインドファイルでDoubleClickイベントを処理し、この呼び出しをViewModelに委任します。
背後でコードを使用し、なおかつMVVM分離を実現するサンプルアプリケーションは、次の場所にあります。
WPFアプリケーションフレームワーク(WAF) - http://waf.codeplex.com
これを.NET 4.5で動作させることができます。単純明快で、サードパーティやコードビハインドは必要ありません。
<ListView ItemsSource="{Binding Data}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="2">
<Grid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding ShowDetailCommand}"/>
</Grid.InputBindings>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image Source="..\images\48.png" Width="48" Height="48"/>
<TextBlock Grid.Row="1" Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
InputBindings
.NET 3.0から利用可能で、Silverlight では利用できないものを追加したいだけです。
添付コマンドの動作とコマンドを使用したい。Marlon Grechは、Attached Command Behaviorsの非常に優れた実装を備えています。これらを使用して、ListViewのItemContainerStyleプロパティにスタイルを割り当て、各ListViewItemにコマンドを設定できます。
ここでは、MouseDoubleClickイベントで発生するコマンドを設定します。CommandParameterは、クリックするデータオブジェクトになります。ここでは、ビジュアルツリーを上に移動して、使用しているコマンドを取得していますが、アプリケーション全体のコマンドを簡単に作成することもできます。
<Style x:Key="Local_OpenEntityStyle"
TargetType="{x:Type ListViewItem}">
<Setter Property="acb:CommandBehavior.Event"
Value="MouseDoubleClick" />
<Setter Property="acb:CommandBehavior.Command"
Value="{Binding ElementName=uiEntityListDisplay, Path=DataContext.OpenEntityCommand}" />
<Setter Property="acb:CommandBehavior.CommandParameter"
Value="{Binding}" />
</Style>
コマンドについては、ICommandを直接実装するか、MVVM Toolkitに含まれているようなヘルパーを使用できます。
acb:
= AttachedCommandBehavior。コードは、回答の最初のリンク
Blend SDK Eventトリガーでこれを行う非常に簡単でクリーンな方法を見つけました。クリーンなMVVM、再利用可能、分離コードなし。
あなたはおそらくこのようなものをすでに持っています:
<Style x:Key="MyListStyle" TargetType="{x:Type ListViewItem}">
まだ使用していない場合は、次のようにListViewItemのControlTemplateを含めます。
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<GridViewRowPresenter Content="{TemplateBinding Content}"
Columns="{TemplateBinding GridView.ColumnCollection}" />
</ControlTemplate>
</Setter.Value>
</Setter>
GridViewRowPresenterは、リスト行要素を構成する「内部」のすべての要素のビジュアルルートになります。ここでトリガーを挿入して、MouseDoubleClickルーティングイベントを探し、次のようにInvokeCommandActionを介してコマンドを呼び出すことができます。
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<GridViewRowPresenter Content="{TemplateBinding Content}"
Columns="{TemplateBinding GridView.ColumnCollection}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding DoubleClickCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</GridViewRowPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
GridRowPresenter(グリッドから始まる)の「上」に視覚要素がある場合は、トリガーをそこに配置することもできます。
残念ながら、MouseDoubleClickイベントはすべてのビジュアル要素から生成されるわけではありません(たとえば、Controlsからではなく、FrameworkElementsからではありません)。回避策は、EventTriggerからクラスを派生させ、ClickCountが2のMouseButtonEventArgsを探すことです。これにより、すべての非MouseButtonEventsとClickCount!= 2のすべてのMoseButtonEventsが効果的に除外されます。
class DoubleClickEventTrigger : EventTrigger
{
protected override void OnEvent(EventArgs eventArgs)
{
var e = eventArgs as MouseButtonEventArgs;
if (e == null)
{
return;
}
if (e.ClickCount == 2)
{
base.OnEvent(eventArgs);
}
}
}
これを書くことができます( 'h'は上記のヘルパークラスの名前空間です):
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<GridViewRowPresenter Content="{TemplateBinding Content}"
Columns="{TemplateBinding GridView.ColumnCollection}">
<i:Interaction.Triggers>
<h:DoubleClickEventTrigger EventName="MouseDown">
<i:InvokeCommandAction Command="{Binding DoubleClickCommand}" />
</h:DoubleClickEventTrigger>
</i:Interaction.Triggers>
</GridViewRowPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
この議論は1年前のものだと思いますが、.NET 4では、この解決策について何か考えはありますか?MVVMの目的は、ファイルの背後にあるコードを排除することではないことに、私は完全に同意します。何かが複雑だからといって、それがより良いという意味ではないことも強く感じます。これが私がコードの背後に置いたものです:
private void ButtonClick(object sender, RoutedEventArgs e)
{
dynamic viewModel = DataContext;
viewModel.ButtonClick(sender, e);
}
ビューの作成時にコマンドをリンクする方が簡単だと思います。
var r = new MyView();
r.MouseDoubleClick += (s, ev) => ViewModel.MyCommand.Execute(null);
BindAndShow(r, ViewModel);
私の場合BindAndShow
は次のようになります(updatecontrols + avalondock):
private void BindAndShow(DockableContent view, object viewModel)
{
view.DataContext = ForView.Wrap(viewModel);
view.ShowAsDocument(dockManager);
view.Focus();
}
ただし、このアプローチは、新しいビューを開く方法に関係なく機能します。
InuptBindingsでrushui からの解決策を確認しましたが、背景が透明に設定された後でも、テキストがないListViewItemの領域に到達できなかったため、別のテンプレートを使用して解決しました。
このテンプレートは、ListViewItemが選択されてアクティブになっている場合に使用します。
<ControlTemplate x:Key="SelectedActiveTemplate" TargetType="{x:Type ListViewItem}">
<Border Background="LightBlue" HorizontalAlignment="Stretch">
<!-- Bind the double click to a command in the parent view model -->
<Border.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ItemSelectedCommand}"
CommandParameter="{Binding}" />
</Border.InputBindings>
<TextBlock Text="{Binding TextToShow}" />
</Border>
</ControlTemplate>
このテンプレートは、ListViewItemが選択されていて非アクティブである場合に使用します。
<ControlTemplate x:Key="SelectedInactiveTemplate" TargetType="{x:Type ListViewItem}">
<Border Background="Lavender" HorizontalAlignment="Stretch">
<TextBlock Text="{Binding TextToShow}" />
</Border>
</ControlTemplate>
これは、ListViewItemに使用されるデフォルトスタイルです。
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border HorizontalAlignment="Stretch">
<TextBlock Text="{Binding TextToShow}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True" />
<Condition Property="Selector.IsSelectionActive" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Template" Value="{StaticResource SelectedActiveTemplate}" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True" />
<Condition Property="Selector.IsSelectionActive" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="Template" Value="{StaticResource SelectedInactiveTemplate}" />
</MultiTrigger>
</Style.Triggers>
</Style>
私が気に入らないのは、TextBlockとそのテキストバインディングの繰り返しです。IIがそれを1つの場所で宣言できるかどうかはわかりません。
これが誰かに役立つことを願っています!
listviewitem
場合、すでに選択されているかどうかは気にしないでしょう。また、listview
スタイルに合わせてハイライト効果を微調整する必要がある場合があることに注意することも重要です。賛成票。
.Net 4.7フレームワークでインタラクティブ機能ライブラリを使用してこの機能を作成することに成功しました。まず、XAMLファイルで名前空間を宣言していることを確認してください
xmlns:i = "http://schemas.microsoft.com/expression/2010/interactivity"
次に、以下のように、ListView内のそれぞれのInvokeCommandActionでイベントトリガーを設定します。
見る:
<ListView x:Name="lv" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=AppsSource}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction CommandParameter="{Binding ElementName=lv, Path=SelectedItem}"
Command="{Binding OnOpenLinkCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Developed By" DisplayMemberBinding="{Binding DevelopedBy}" />
</GridView>
</ListView.View>
</ListView>
上記のコードを適用するだけで、ViewModelでダブルクリックイベントを機能させることができますが、完全なアイデアを得るために、サンプルからModelクラスとView Modelクラスを追加しました。
モデル:
public class ApplicationModel
{
public string Name { get; set; }
public string DevelopedBy { get; set; }
}
モデルを見る:
public class AppListVM : BaseVM
{
public AppListVM()
{
_onOpenLinkCommand = new DelegateCommand(OnOpenLink);
_appsSource = new ObservableCollection<ApplicationModel>();
_appsSource.Add(new ApplicationModel("TEST", "Luis"));
_appsSource.Add(new ApplicationModel("PROD", "Laurent"));
}
private ObservableCollection<ApplicationModel> _appsSource = null;
public ObservableCollection<ApplicationModel> AppsSource
{
get => _appsSource;
set => SetProperty(ref _appsSource, value, nameof(AppsSource));
}
private readonly DelegateCommand _onOpenLinkCommand = null;
public ICommand OnOpenLinkCommand => _onOpenLinkCommand;
private void OnOpenLink(object commandParameter)
{
ApplicationModel app = commandParameter as ApplicationModel;
if (app != null)
{
//Your code here
}
}
}
DelegateCommandクラスの実装が必要な場合。
ここでは、両方で行われていることを取得行動だListBox
とはListView
。
public class ItemDoubleClickBehavior : Behavior<ListBox>
{
#region Properties
MouseButtonEventHandler Handler;
#endregion
#region Methods
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseDoubleClick += Handler = (s, e) =>
{
e.Handled = true;
if (!(e.OriginalSource is DependencyObject source)) return;
ListBoxItem sourceItem = source is ListBoxItem ? (ListBoxItem)source :
source.FindParent<ListBoxItem>();
if (sourceItem == null) return;
foreach (var binding in AssociatedObject.InputBindings.OfType<MouseBinding>())
{
if (binding.MouseAction != MouseAction.LeftDoubleClick) continue;
ICommand command = binding.Command;
object parameter = binding.CommandParameter;
if (command.CanExecute(parameter))
command.Execute(parameter);
}
};
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewMouseDoubleClick -= Handler;
}
#endregion
}
これは、親を見つけるために使用される拡張クラスです。
public static class UIHelper
{
public static T FindParent<T>(this DependencyObject child, bool debug = false) where T : DependencyObject
{
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
if (parentObject is T parent)
return parent;
else
return FindParent<T>(parentObject);
}
}
使用法:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:coreBehaviors="{{Your Behavior Namespace}}"
<ListView AllowDrop="True" ItemsSource="{Binding Data}">
<i:Interaction.Behaviors>
<coreBehaviors:ItemDoubleClickBehavior/>
</i:Interaction.Behaviors>
<ListBox.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding YourCommand}"/>
</ListBox.InputBindings>
</ListView>