在按钮命令上,将 UserControl 上的 ItemControl 项传递到 ViewModel

问题描述 投票:0回答:1

我想是时候寻求帮助了!我有一个列出 UserControls 的 ItemsControl(称为CompoundCardControl):

<ScrollViewer Grid.Row = "1" VerticalScrollBarVisibility="Auto">
    <ItemsControl ItemsSource="{Binding Method.Compounds}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <control:CompoundCardControl/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</ScrollViewer>

UserControl 包含一个按钮,用于从 ListView(和 ViewModel)中删除项目:

<Grid Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="auto"/>
        <ColumnDefinition Width="auto"/>
        <ColumnDefinition Width="auto"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="25"/>
    </Grid.ColumnDefinitions>

    <TextBlock Grid.Column="0" Margin="5,0,0,0" Text="{Binding Name}" />
    <TextBlock Grid.Column="1" Margin="5,0,0,0" Foreground="Gray" FontSize="10" VerticalAlignment="Center" Text="RT"/>
    <TextBlock Grid.Column="2" Margin="5,0,0,0" Text="{Binding RetentionTime , StringFormat= '\{0\} minutes'}" />
    <Button Grid.Column="3" HorizontalAlignment="Right" Style="{StaticResource menuBarButton}"
            Command="{Binding DataContext.DeleteCompoundCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"
            CommandParameter="{Binding Compound}">
        <Image Width="12" Height="12"  Source="/Resources/Icons/Delete.png"/>
    </Button>
</Grid>

这是我的父窗口的 ViewModel(但它显然不起作用)”:

public class MethodDialogViewModel : ViewModelBase
{
    //properties
    public DialogModeEnum DialogMode { get; private set; }
    public string WindowTitle { get { return DialogMode == DialogModeEnum.Add ? "New method" : "Edit method"; } }
    public string ButtonText { get { return DialogMode == DialogModeEnum.Add ? "Add" : "Update"; } }
    public Method Method { get; set; }
    public MethodCompound Compound { get; set; } = new MethodCompound();

    //constructor
    public MethodDialogViewModel(Method method, DialogModeEnum dialogMode)
    {
        DialogMode = dialogMode;    
        Method = method;
    }

    //commands
    public RelayCommand DeleteCompoundCommand => new(execute => DeleteCompound(Compound));
    private void DeleteCompound(MethodCompound methodCompound)
    {
    //Logic not implimented. I just want to see if I can get the object "methodCompound"
        if (methodCompound != null)
        {
            MessageBox.Show(methodCompound.ID);
        }
        else
        {
            MessageBox.Show("Please select a compound.", "Delete compound", MessageBoxButton.OK, MessageBoxImage.Information);
        }
    }
}

基本上我想将 ItemsControl 中的 Item(实际上是底层对象)传递给 ViewModel。我怎么做?还是我完全搞砸了?

c# wpf mvvm
1个回答
0
投票

您的代码存在一些奇怪之处,这似乎导致了为什么没有任何内容按预期工作。

  • RelayCommand 委托似乎是错误的。根据参数名称

    execute
    ,您似乎得到了错误的委托签名。
    RelayCommand
    实现的原始签名是
    ICommand.Execute(object commandParameter): void
    。这意味着 lambda 的
    execute
    参数应命名为,例如
    commandParameter
    。然后您实际上必须将该参数传递给执行委托。否则,
    Button.CommandParameter
    值将丢失,即不转发:

    公共 RelayCommand 删除复合命令 => new(commandParameter => DeleteCompound((MethodCompound )commandParameter));

  • MethodDialogViewModel.Compound
    属性始终返回相同的实例

  • Grid.Width
    CompoundCardControl
    上的绑定是多余的,因为
    Grid
    会自然拉伸以填充可用空间。

  • 因为您想要滚动 ItemsControl 内部

    的项目,而不是 
    ItemsControl
     本身,所以必须将 
    ScrollViewer
     添加到 
    ControlTemplate
    ItemsControl
     中,它包裹着 
    ItemsPresenter

    <ControlTemplate TargetType="ItemsControl"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"> <ScrollViewer> <ItemsPresenter /> </ScrollViewer> </Border> </ControlTemplate>
    
    
  • 请注意,

    ItemsControl

    不提供任何性能功能(例如 UI 虚拟化),这可以显着改善较大列表或包含昂贵项目或项目容器的列表的体验。
    另一方面,
    ListBox
     是一种高级的 
    ItemsControl
    ,带有滚动和
    具有 UI 虚拟化和一些有用的属性,例如返回当前所选项目的 
    ListBox.SelectedItem
     属性。在您的情况下,您可以将其绑定到 
    MethodDialogViewModel.Compound
     属性。要消除突出显示,您只需覆盖 
    ListBoxItem.Template
    :

    <!-- A ListBoxItem without highlighting. You can add this template to a resource to reuse it. --> <ControlTemplate x:Key="NoHighlightListBoxItemTemplate" TargetType="ListBoxItem"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"> <ContentPresenter /> </Border> </ControlTemplate>
    
    
  • 一些一般性说明:自定义控件(例如

    UserControl

    )永远不应该使用自己的 
    DataContext
    。例如,当 
    DataContext
     发生变化时,控件就会损坏。由于 
    Binding.RelativeSource
     的目标元素是自定义控件之外的,因此将自定义控件移动到可视化树中的其他位置也会破坏自定义控件。为了防止这种情况,请添加请求将所需数据传递到自定义控件的依赖属性,例如通过数据绑定。然后将内部控件绑定到这些依赖属性。目前尚不清楚为什么在这种情况下使用 
    UserControl
    。您可以通过直接在 
    UserControl
     中定义项目的布局来安全地避免 
    DataTemplate
     的开销。如果需要分隔,您可以将 
    DataTemplate
     移动到其自己的文件中。
    这与解决您的问题无关,但如果您关心良好的控制设计,则与此相关。这些是一些基础知识。

CompoundCardControl.xaml.cs

public partial class CompoundCardControl : UserControl { public MethodCompound Compound { get => (MethodCompound)GetValue(CompoundProperty); set => SetValue(CompoundProperty, value); } public static readonly DependencyProperty CompoundProperty = DependencyProperty.Register( "Compound", typeof(MethodCompound), typeof(CompoundCardControl), new PropertyMetadata(default)); public ICommand DeleteCommand { get => (ICommand)GetValue(DeleteCommandProperty); set => SetValue(DeleteCommandProperty, value); } public static readonly DependencyProperty DeleteCommandProperty = DependencyProperty.Register( "DeleteCommand", typeof(ICommand), typeof(CompoundCardControl), new PropertyMetadata(default)); public CompoundCardControl() { InitializeComponent(); } }

CompoundCardControl.xaml

<UserControl x:Name="Root"> <Button Style="{StaticResource menuBarButton}" Command="{Binding ElementName=Root, Path=DeleteCommand}" CommandParameter="{Binding ElementName=Root, Path=Compound}"> <Image Source="/Resources/Icons/Delete.png"/> </Button> </UserControl>

MainWindow.xaml

<Window> <Window.DataContext> <MethodDialogViewModel /> </Window.DataContext> <Window.Resources> <!-- A ListBoxItem without highlighting --> <ControlTemplate x:Key="NoHighlightListBoxItemTemplate" TargetType="ListBoxItem"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <ContentPresenter /> </Border> </ControlTemplate> </Window.Resources> <ListBox ItemsSource={Binding Method.Compounds}"> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="Template" Value="{StaticResource NoHighlightListBoxItemTemplate}" /> </Style> </ListBox.ItemContainerStyle> <ListBox.ItemTemplate> <DataTemplate DataType="{x:Type MethodCompound}"> <control:CompoundCardControl Command="{Biding DeleteCompoundCommand}" Compound="{Binding}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Window>

MethodDialogViewModel.cs

public class MethodDialogViewModel : ViewModelBase { //properties public DialogModeEnum DialogMode { get; private set; } public string WindowTitle { get { return DialogMode == DialogModeEnum.Add ? "New method" : "Edit method"; } } public string ButtonText { get { return DialogMode == DialogModeEnum.Add ? "Add" : "Update"; } } public Method Method { get; set; } // This does never change. Consider to bind it to the ListBox.SelectedItem property public MethodCompound Compound { get; set; } = new MethodCompound(); //constructor public MethodDialogViewModel(Method method, DialogModeEnum dialogMode) { DialogMode = dialogMode; Method = method; } //commands public RelayCommand DeleteCompoundCommand => new(ExecuteDeleteCompoundCommand); private void ExecuteDeleteCompoundCommand(object commandParameter) => DeleteCompound((MethodCompound)commandParameter); private void DeleteCompound(MethodCompound methodCompound) { // methodCompund will never be NULL in the current scenario this.Method.Compunds.Remove(methodCompound); } }
    
© www.soinside.com 2019 - 2024. All rights reserved.