Kamil Solecki
Kamil Solecki

Reputation: 1117

Returning ViewModels' instance through CommandParameters

I have a Parent ViewModel, bound to the Parent Window. Inside, there is an ItemsControl, that is Bound to a Collection of Child ViewModels. The ItemsControl creates UserControls (Child Views) that get their DataContext as ChildViewModel (automatically, according to how ItemsControl Works).

It might be worth noting, that I am using Castle Windsor as IoC. I have also tried resolving the child ViewModels through a (currently working) abstract factory, but it didn't help. I even tried with the child ViewModel registered as a singleton, but still - cannot be found inside the collection.

So I dumbed down the problem question to simple class instantiation.

Now, what I wanted to:

Each Reminder (UserControl) has a Button, that represents closing. This button is bound to the Parent ViewModel like this:

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

Where level1Listener is the name of the ItemsControl.

So far everything works fine, UserControls get created and all bindings work. But:

When I click the Close button, the ViewModel isn't removed from the collection - even though the bound command executes. It seems, that the instances of ReminderBoxViewModel returned by ANY of the UserControls, do not exist in the collection - but the UserControls even showing up depend directly on their existence.

When i simply remove an element at a certain index - all works fine.

How could the UC return something different as its' Data Context?

How is this possible, or am I missing something entirely?

This is my PhoneWindow View:

<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 (View) Code behind:

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);
    }
}
}

And this is the 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());
    }

Lastly, my userControl Code:

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();


    }
}
}

Reminder 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>

As for The ReminderViewModel, it is currently empty, except for an inheritace from (also currently empty) interface IReminderViewModel.

Upvotes: 1

Views: 71

Answers (1)

nkoniishvt
nkoniishvt

Reputation: 2521

Your issue seems to be that althought you're setting your CommandParameter DP to a Binding to the current ReminderBoxViewModel, you aren't using it anywhere in the ReminderBox.

In the Reminder XAML you correctly bind the Command of the Button to the CloseCommand of your ReminderBox, you aren't setting the Button's CommandParameter to the CommandParameter of your ReminderBox.

The relevant code of the ReminderBox's XAML:

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

Upvotes: 1

Related Questions