【问题标题】:Returning ViewModels' instance through CommandParameters通过 CommandParameters 返回 ViewModels 的实例
【发布时间】:2017-09-21 01:22:00
【问题描述】:

我有一个父 ViewModel,绑定到父窗口。在内部,有一个 ItemsControl,它绑定到子 ViewModel 的集合。 ItemsControl 创建 UserControls(子视图),将其 DataContext 作为 ChildViewModel(根据 ItemsControl 的工作方式自动)。

值得注意的是,我使用的是温莎城堡作为 IoC。 我还尝试通过(当前工作的)抽象工厂解决子 ViewModels,但它没有帮助。我什至尝试将子 ViewModel 注册为单例,但仍然无法在集合中找到。

所以我把问题简化为简单的类实例化。

现在,我想要:

每个提醒(用户控件)都有一个按钮,代表关闭。 这个按钮绑定到父 ViewModel,如下所示:

CloseCommand="{Binding ElementName=level1Listener, Path=DataContext.ReminderBoxCloseCommand}"

其中level1ListenerItemsControl 的名称。

到目前为止,一切正常,用户控件已创建,所有绑定都正常工作。但是:

当我单击关闭按钮时,ViewModel 不会从集合中删除 - 即使绑定命令执行。似乎,任何 UserControls 返回的 ReminderBoxViewModel 的实例都不存在于集合中 - 但 UserControls 的出现甚至直接取决于它们的存在。

当我简单地删除某个索引处的元素时 - 一切正常。

UC 如何返回与其数据上下文不同的东西?

这怎么可能,或者我完全错过了什么?

这是我的 PhoneWindow 视图:

<Window x:Class="NoContact.Views.PhoneWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:NoContact.Views"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:avalon="http://icsharpcode.net/sharpdevelop/avalonedit"
    xmlns:usercontrols="clr-namespace:NoContact.UserControls"
    xmlns:viewModels="clr-namespace:NoContact.ViewModels"
    mc:Ignorable="d"
    Title="PhoneWindow" Height="600" Width="980"
    Background="#22282a">

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="600" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
    <Border Grid.Column="0" Margin="10, 10" Style="{StaticResource phoneWindowBorderStyle}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="80"/>
                <RowDefinition Height="180" />
                <RowDefinition Height="*" />
                <RowDefinition Height="100"/>
            </Grid.RowDefinitions>
            <Border Grid.Row="0" Margin="-1,-1" Style="{StaticResource phoneWindowBorderStyle}">
                <StackPanel Orientation="Horizontal">
                    <Image Margin="15">
                        <Image.Source>
                            <DrawingImage>
                                <DrawingImage.Drawing>
                                    <DrawingGroup>
                                        <GeometryDrawing Brush="#ffcd22"
                                             Geometry="{StaticResource phoneIconGeometry}" />
                                    </DrawingGroup>
                                </DrawingImage.Drawing>
                            </DrawingImage>
                        </Image.Source>
                    </Image>
                    <TextBlock VerticalAlignment="Center" FontSize="30" Style="{StaticResource phoneWindowTextBlockStyle}">Telefon</TextBlock>
                </StackPanel> 
            </Border>

            <Grid Grid.Row="1">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid Grid.Column="0">
                    <DockPanel LastChildFill="False">
                        <DockPanel LastChildFill="True" Margin="10,20,0,0" DockPanel.Dock="Top">
                            <TextBlock Style="{StaticResource phoneWindowTextBlockStyle}">Imię:</TextBlock>
                            <TextBox Height="30" Margin="46,0,10,0" Style="{StaticResource phoneWindowTextBoxStyle}"></TextBox>
                        </DockPanel>
                        <DockPanel LastChildFill="True" Margin="10,20,0,0" DockPanel.Dock="Top">
                            <TextBlock Style="{StaticResource phoneWindowTextBlockStyle}">Nazwisko:</TextBlock>
                            <TextBox Height="30" Margin="10,0,10,0" Style="{StaticResource phoneWindowTextBoxStyle}"></TextBox>
                        </DockPanel>
                        <DockPanel LastChildFill="True" Margin="10,20,0,0" DockPanel.Dock="top">
                            <TextBlock Style="{StaticResource phoneWindowTextBlockStyle}">Data:</TextBlock>
                            <usercontrols:DateTimePicker Height="30" Margin="40,0,10,0" BorderBrush="#ffcd22" BorderThickness="1" Background="#15151a" Foreground="#ffcd22"/>
                        </DockPanel>
                    </DockPanel>
                </Grid>
                <Grid Grid.Column="1">
                    <DockPanel LastChildFill="False">
                        <DockPanel LastChildFill="True" Margin="10,20,0,0" DockPanel.Dock="Top">
                            <TextBlock Style="{StaticResource phoneWindowTextBlockStyle}">Email:</TextBlock>
                            <TextBox Height="30" Margin="26,0,10,0" Style="{StaticResource phoneWindowTextBoxStyle}"></TextBox>
                        </DockPanel>
                        <DockPanel LastChildFill="True" Margin="10,20,0,0" DockPanel.Dock="Top">
                            <TextBlock Style="{StaticResource phoneWindowTextBlockStyle}">Telefon:</TextBlock>
                            <TextBox Height="30" Margin="10,0,10,0" Style="{StaticResource phoneWindowTextBoxStyle}"></TextBox>
                        </DockPanel>
                        <DockPanel LastChildFill="True" Margin="10,20,0,0" DockPanel.Dock="top">
                            <TextBlock Style="{StaticResource phoneWindowTextBlockStyle}">Nr. Zamówienia:</TextBlock>
                            <TextBox Height="30" Margin="10,0,10,0" Style="{StaticResource phoneWindowTextBoxStyle}"></TextBox>
                        </DockPanel>
                    </DockPanel>
                </Grid>
            </Grid>
            <Grid Grid.Row="2">
                <avalon:TextEditor Margin="10,10" Style="{StaticResource phoneWindowAvalonEditStyle}" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" WordWrap="True">

                </avalon:TextEditor>
            </Grid>
            <Border Grid.Row="3" Margin="-1,0,-1,-1" Style="{StaticResource phoneWindowBorderStyle}">
                <Grid>
                    <Button Width="170" HorizontalAlignment="Right" Margin="10" Style="{StaticResource flatButtonStyle}">Gotowe</Button>
                    <Button Width="170" HorizontalAlignment="Left" Margin="10" Style="{StaticResource flatButtonStyle}">Anuluj</Button>
                </Grid>
            </Border>
        </Grid>
    </Border>

    <Border Grid.Column="1" Margin="10,10" Style="{StaticResource phoneWindowBorderStyle}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="80" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Border Grid.Row="0" Margin="-1,-1,-1,-1" Style="{StaticResource phoneWindowBorderStyle}">
                <usercontrols:ImageButton x:Name="addButton" Style="{StaticResource addImageButtonUCStyle}" Text="Dodaj Termin" ClickCommand="{Binding Path=AddReminderCommand}">

                </usercontrols:ImageButton>
            </Border>
            <Border Grid.Row="1" Margin="-1,0,-1,-1" Style="{StaticResource phoneWindowBorderStyle}">
                <ScrollViewer VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden" Background="#15151a">
                    <ItemsControl x:Name="level1Listener" ItemsSource="{Binding Path=Reminders, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <StackPanel />
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                        <ItemsControl.ItemTemplate>
                            <DataTemplate DataType="{x:Type viewModels:ReminderBoxViewModel}">
                                <StackPanel>
                                    <usercontrols:ReminderBox VerticalAlignment="Top" Background="#22282a" Foreground="#ffcd22" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, 
                                                              AncestorType={x:Type ScrollViewer}}, Path=ActualWidth}" Height="140" CloseCommand="{Binding ElementName=level1Listener, Path=DataContext.ReminderBoxCloseCommand}"
                                                              CommandParameter="{Binding}"/>
                                    <Rectangle Height="1" Margin="10,0" Fill="#ffcd22" />
                                </StackPanel>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </ScrollViewer>
            </Border>
        </Grid>
    </Border>
</Grid>

PhoneWindow(视图)背后的代码:

namespace NoContact.Views
{
/// <summary>
/// Interaction logic for phoneWindow.xaml
/// </summary>
public partial class PhoneWindow : Window, IView
{
    public IViewModel ViewModel { get; private set; }
    private IWindowManager _windowManager;

    public PhoneWindow(IViewFactory context, IWindowManager windowManager)
    {
        InitializeComponent();

        _windowManager = windowManager;

        var obj = context.Create<IPhoneWindowViewModel>();
        ViewModel = obj;
        this.DataContext = obj;
        context.Release(obj);
    }

    protected override void OnClosed(EventArgs e)
    {
        _windowManager.RemoveFromWindowList(this);
        base.OnClosed(e);
    }
}
}

这是 MainViewModel:

public class PhoneWindowViewModel : BindableBase, IPhoneWindowViewModel
{
    private IWindowManager _windowManager;

    private ObservableCollection<IReminderBoxViewModel> _reminders;

    public PhoneWindowViewModel(IWindowManager windowManager)
    {
        _windowManager = windowManager;

        _reminders = new ObservableCollection<IReminderBoxViewModel>();
    }

    public ICommand ReminderBoxCloseCommand { get { return new RelayCommand<ReminderBoxViewModel>(CloseReminderBox); } }
    public ICommand AddReminderCommand { get { return new RelayCommand(AddReminder, () => true); } }

    public ObservableCollection<IReminderBoxViewModel> Reminders
    {
        get { return _reminders; }
        set
        {
            _reminders = value;
            OnPropertyChanged();
        }
    }

    /// <summary>
    /// Delegates closing the window, associated with 
    /// this ViewModel instance.
    /// </summary>
    private void CloseWindow()
    {
        _windowManager.CloseWindow(this);
    }

    /// <summary>
    /// "Closes" the Reminder Box by deleting it from the Collection 
    /// </summary>
    private void CloseReminderBox(ReminderBoxViewModel viewModel)
    {
        Reminders.Remove(viewModel);
        var index = Reminders.IndexOf(viewModel);
        System.Windows.MessageBox.Show(index.ToString());

    }

    /// <summary>
    /// Adds the reminder to the Collection
    /// </summary>
    private void AddReminder()
    {
        Reminders.Add(new ReminderViewModel());
    }

最后,我的 userControl 代码:

namespace NoContact.UserControls
{
/// <summary>
/// Interaction logic for ReminderBox.xaml
/// </summary>
public partial class ReminderBox : UserControl
{
    #region Dependency Properties

    public Brush CloseButtonBrush
    {
        get { return (Brush)GetValue(CloseButtonBrushProperty); }
        set { SetValue(CloseButtonBrushProperty, value); }
    }

    public new Brush Background
    {
        get { return (Brush)GetValue(BackgroundProperty); }
        set { SetValue(BackgroundProperty, value); }
    }

    public new Brush Foreground
    {
        get { return (Brush)GetValue(ForegroundProperty); }
        set { SetValue(ForegroundProperty, value); }
    }

    public ICommand CloseCommand
    {
        get { return (ICommand)GetValue(CloseCommandProperty); }
        set { SetValue(CloseCommandProperty, value); }
    }

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

    public static readonly DependencyProperty CloseButtonBrushProperty =
        DependencyProperty.Register("CloseButtonBrush", typeof(Brush), typeof(ReminderBox), new UIPropertyMetadata(null));

    public static readonly new DependencyProperty BackgroundProperty =
       DependencyProperty.Register("Background", typeof(Brush), typeof(ReminderBox), new UIPropertyMetadata(null));

    public static readonly new DependencyProperty ForegroundProperty =
      DependencyProperty.Register("Foreground", typeof(Brush), typeof(ReminderBox), new UIPropertyMetadata(null));

    public static readonly DependencyProperty CloseCommandProperty =
        DependencyProperty.Register("CloseCommand", typeof(ICommand), typeof(ReminderBox), new UIPropertyMetadata(null));

    public static readonly DependencyProperty CommandParameterProperty =
        DependencyProperty.Register("CommandParameter", typeof(object), typeof(ReminderBox), new UIPropertyMetadata(null));

    #endregion



    public ReminderBox()
    {
        InitializeComponent();


    }
}
}

提醒 XAML:

<UserControl x:Class="NoContact.UserControls.ReminderBox"
         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" 
         xmlns:local="clr-namespace:NoContact.UserControls"
         xmlns:usercontrols="clr-namespace:NoContact.UserControls"
         xmlns:helpers="clr-namespace:NoContact.Helpers"
         mc:Ignorable="d" 
         x:Name="ReminderBoxUC"
         d:DesignHeight="130" d:DesignWidth="400">
<Grid>
    <Border Background="{Binding ElementName=ReminderBoxUC, Path=Background}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="0.5*" />
                <RowDefinition Height="0.5*" />
                <RowDefinition Height="1*" />
            </Grid.RowDefinitions>

            <Button Grid.Row="0" Width="20" Height="20" Style="{StaticResource flatButtonStyle}" Background="Transparent" Foreground="Transparent"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Top"
                    Margin="0,10,10,0"
                    Command="{Binding ElementName=ReminderBoxUC, Path=CloseCommand}">
                <Border Name="closeButtonBackgroundBorder" CornerRadius="5" Style="{StaticResource CloseButtonReminderBox_BorderUCStyle}">
                    <Image Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=ActualWidth}"
                       Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=ActualHeight}"
                       HorizontalAlignment="Center" VerticalAlignment="Center">
                        <Image.Source>
                            <DrawingImage>
                                <DrawingImage.Drawing>
                                    <DrawingGroup>
                                        <GeometryDrawing Brush="{Binding ElementName=ReminderBoxUC, Path=CloseButtonBrush}" Geometry="{StaticResource closeIconGeometry}" />
                                    </DrawingGroup>
                                </DrawingImage.Drawing>
                            </DrawingImage>
                        </Image.Source>
                    </Image>
                </Border>
            </Button>

            <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="10,0">
                <TextBlock Foreground="{Binding ElementName=ReminderBoxUC, Path=Foreground}"
                           VerticalAlignment="Center" HorizontalAlignment="Left">Przypomnij:
                </TextBlock>
                <RadioButton Name="RemindOnceRadioButton" IsChecked="True"
                                 Foreground="{Binding ElementName=ReminderBoxUC, Path=Foreground}"
                                 Style="{StaticResource RadioButtonStyle}"
                                 VerticalAlignment="Center" Margin="25,0,0,0">1 Raz</RadioButton>
                <RadioButton Name="RemindMultipleRadioButton"   
                             Foreground="{Binding ElementName=ReminderBoxUC, Path=Foreground}"
                                 Style="{StaticResource RadioButtonStyle}"
                                 VerticalAlignment="Center" Margin="25,0,0,0">Wielokrotnie</RadioButton>
            </StackPanel>

            <DockPanel Grid.Row="2"
                            Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid}}, Path=ActualWidth}">
                <usercontrols:DateTimePicker Height="25" Margin="10,0" BorderBrush="#ffcd22" BorderThickness="1" Foreground="#ffcd22" Background="#15151a" Width="150" />
                <ComboBox Width="150" Height="25" HorizontalAlignment="Right" Margin="10,0" Style="{StaticResource comboBoxStyle}" ItemsSource="{Binding Source={helpers:EnumBindingSource {x:Type local:TimePeriods}}}"
                          SelectedIndex="0" />
            </DockPanel>
        </Grid>
    </Border>        
</Grid>
<UserControl.Resources>
    <Style TargetType="local:ReminderBox">
        <Setter Property="CloseButtonBrush" Value="#ffcd22" />
        <Style.Triggers>
            <DataTrigger Binding="{Binding ElementName=closeButtonBackgroundBorder, Path=IsMouseOver}" Value="True">
                <Setter Property="CloseButtonBrush" Value="#22282a" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</UserControl.Resources>

至于 ReminderViewModel,它目前是空的,除了继承自(目前也是空的)接口 IRminderViewModel。

【问题讨论】:

  • 您的Close Command 应该从PhoneWindowViewModel 执行。这样,您可以通过命令参数传递 ViewModel,即 CommandParameter={Bindinig .} 并在您的 PhoneWindowViewModel 中将其从集合中删除。绑定到 PhoneWindowViewModel 应该看起来像 Command="{Binding DataContext.CloseCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
  • @XAMlMAX 我不确定我是否理解你的意思,因为对我来说这正是现在正在发生的事情。该集合驻留在 PhoneWindowViewModel 中,命令也是如此。问题是 - 命令本身正确执行,只是使用 CommandParameters 返回的提醒控件的视图模型似乎不存在于集合中。
  • 好的。我现在可以看到整个事情了。你得到这行var index = Reminders.IndexOf(viewModel);的索引了吗?
  • @XAMlMAX 是的 - 但这一行仅用于调试 - 查看从命令参数返回的视图模型是否可以在集合中的任何位置找到。令人惊讶的是,它返回 -1。实际的工作应该在上面的行中完成:Reminders.remove(viewModel);
  • 您尝试过 CommandParameter="{Binding}" 吗?还要确保您没有在某处显式设置 ReminderBox 的 DataContext。

标签: c# wpf mvvm ioc-container


【解决方案1】:

您的问题似乎是,尽管您将 CommandParameter DP 设置为与当前 ReminderBoxViewModel 的绑定,但您并没有在 ReminderBox 中的任何地方使用它。

在 Reminder XAML 中,您正确地将 Button 的 Command 绑定到 ReminderBox 的 CloseCommand,您没有将 Button 的 CommandParameter 设置为 ReminderBox 的 CommandParameter

ReminderBox的XAML相关代码:

<Button ...
        Command="{Binding ElementName=ReminderBoxUC, Path=CloseCommand}"
        CommandParameter"{Binding ElementName=ReminderBoxUC, Path=CommandParameter}">
    ...
</Button>

【讨论】:

  • 我太专注于更复杂的原因,而忽略了最简单的原因。就是这样,谢谢你:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-29
相关资源
最近更新 更多