답변:
코드 숨김은 전혀 나쁜 것이 아닙니다. 불행히도 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를 아주 잘 구현했습니다. 이를 사용하여 각 ListViewItem에 대한 명령을 설정하는 ListView의 ItemContainerStyle 속성에 스타일을 할당 할 수 있습니다.
여기서는 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 이벤트 트리거로이 작업을 수행하는 매우 쉽고 깔끔한 방법을 찾았습니다. 재사용 가능하고 코드 숨김이없는 깨끗한 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 이벤트는 모든 시각적 요소에서 생성되지는 않습니다 (예를 들어 FrameworkElements가 아닌 Controls에서 생성됨). 해결 방법은 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과 해당 텍스트 바인딩의 반복입니다. 한 위치에서이를 선언 할 수 있는지 모르겠습니다.
나는 이것이 누군가를 돕기를 바랍니다!
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>