Catel..UserControl exposing a Command

Mar 26, 2012 at 12:46 PM

Hello,

I was just refactoring an older project to use Catel. During this, I splitted my MainWindow so that a part of it now lies within a new user control.

I have a list of items in my new UserControl. When the selection changes, I need to react in the MainViewModel. Originally I directly binded the SelectionChange with an EventToCommand to a Command within the MainViewModel.

How should I handle this now?

I tried to create a Command for my UserControl. The code of the Usercontrol then would fire the command when it sees it necessary. The MainWindow could then bind the command to the MainViewModel. I believe that this would be the cleares separation, but I don't succeed creating a command within the UserControl.

Otherwise I could fire an Event within the UserControl and again use the EventToCommand to bind to the MainViewModel, but I don't like this.

Any hints?

Thanks, Gorroux.

Mar 26, 2012 at 1:32 PM
Edited Mar 26, 2012 at 1:33 PM

What I have at the moment (not working):

PresetManagerControl

    public partial class PresetManagerControl : Catel.Windows.Controls.UserControl
    {

        protected override Type GetViewModelType()
        {
            return typeof(PresetManagerViewModel);
        }

        public PresetManagerControl()
        {
            InitializeComponent();
            Command = new Command(() => { });
        }

        public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(PresetManagerControl));

        //shortened        
        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        private void PresetList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (Command == null)
                Command = new Command(() => { });

            if (PresetList.SelectedItem != null && Command != null)
                Command.Execute(PresetList.SelectedItem);
        }

    }

Weird thing here: Command is null if PresetList_SelectionChanged is called, but it was successfully set in constructor before. And how should I instanciate the Command anyway.

<catel:UserControl x:Class="Kav.Datagate.Tools.GacManager.View.PresetManagerControl"...>
...
            <ListView Name="PresetList" Grid.Row="1" ItemsSource="{Binding Path=PresetList, Mode=TwoWay}"
                          BorderThickness="0" IsSynchronizedWithCurrentItem="True"
                          SelectionChanged="PresetList_SelectionChanged"
                          >

PresetManagerViewModel:

public class PresetManagerViewModel : ViewModelBase
{
    public PresetManagerViewModel(PresetManager presetManager)
    {
        this.PresetManager = presetManager;
    }
   ...

MainWindow:

<LocalControls:PresetManagerControl DataContext="{Binding PresetManager}" Command="{Binding Path=ApplyNewPresetCommand}" />

MainWindowViewModel:

#region PROPERTY PresetManager
 public PresetManager PresetManager
{
 get { return GetValue<PresetManager>(PresetManagerProperty); }
 set { SetValue(PresetManagerProperty, value); }
}
public static readonly PropertyData PresetManagerProperty = RegisterProperty("PresetManager", typeof(PresetManager), null);
#endregion

 

 

#region COMMAND ApplyNewPresetCommand
public Command<Preset> ApplyNewPresetCommand { get; set; }
        
void OnApplyNewPresetExecute(Preset preset)
{
    ...
}
#endregion


No exceptions, but no command execution on MainViewModel.
Coordinator
Mar 26, 2012 at 5:59 PM

Hi,

There is no need for complex code. I think you have 3 options here:

1) InterestedIn attribute (simplest solution)

2) MessageMediator (medium solution)

3) Manager (what you have now, complex solution)

If the InterestedIn attribute works, go for that. It takes away any dangers of memory leaks since it does all the event attaching/detaching for you.

Mar 27, 2012 at 8:37 AM
Edited Mar 27, 2012 at 8:38 AM

Hi,

I'm going for the InterestedIn attribute, since I found out that I can hook up to a command (I was just thinking of properties, which I did not like).

I still have a problem and two questions:

PresetManagerControl:

<ListView Name="PresetList" Grid.Row="1" ItemsSource="{Binding Path=PresetList, Mode=TwoWay}"
                BorderThickness="0" IsSynchronizedWithCurrentItem="True">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <catel:EventToCommand 
Command
="{Binding Path=PresetSelectedCommand, diag:PresentationTraceSources.TraceLevel=Medium}" CommandParameter="{Binding Path=SelectedItem, ElementName=PresetList}" DisableAssociatedObjectOnCannotExecute="False" /> </i:EventTrigger> </i:Interaction.Triggers> <ListView.View> <GridView ColumnHeaderContainerStyle="{StaticResource NoHeaderStyle}"> <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}"/> <GridViewColumn Header="Filter" DisplayMemberBinding="{Binding Path=Filter}"/> </GridView> </ListView.View> </ListView>

Here the SelectionChanged event is mapped to a Command within the PresetManagerViewModel:

public class PresetManagerViewModel : ViewModelBase
{
    #region Constructor & destructor
    /// <summary>
    /// Initializes a new instance of the <see cref="PresetManagerViewModel"/> class.
    /// </summary>
    public PresetManagerViewModel(PresetManager presetManager)
    {
        PresetSelectedCommand = new Command(OnPresetSelectedCommandExecute);
        this.PresetManager = presetManager;
    }
    #endregion

    [Model]
    public PresetManager PresetManager
    {
        get { return GetValue<PresetManager>(PresetManagerProperty); }
        private set { SetValue(PresetManagerProperty, value); }
    }
    public static readonly PropertyData PresetManagerProperty = RegisterProperty("PresetManager", typeof(PresetManager));

    public override string Title { get { return "Preset Manager"; } }

    #region COMMAND PresetSelectedCommand
    public Command PresetSelectedCommand { get; private set; }
 
    private void OnPresetSelectedCommandExecute()
    {
            
    }
    #endregion 
}

But the command is never executed within the UserControls ViewModel. The reason seems to be, that WPF tries to bind to the PresetManager (model) property, instead of the PresetManagerViewModel class. I tried some binding modifications, but none seem to work. This is the trace from the binding:

System.Windows.Data Warning: 60 : BindingExpression (hash=53625532): Attach to Catel.Windows.Interactivity.EventToCommand.Command (hash=37987662)
System.Windows.Data Warning: 62 : BindingExpression (hash=53625532): Use Framework mentor 
System.Windows.Data Warning: 65 : BindingExpression (hash=53625532): Resolving source 
System.Windows.Data Warning: 67 : BindingExpression (hash=53625532): Framework mentor not found
System.Windows.Data Warning: 63 : BindingExpression (hash=53625532): Resolve source deferred
System.Windows.Data Warning: 93 : BindingExpression (hash=53625532): Got InheritanceContextChanged event from EventToCommand (hash=37987662)
System.Windows.Data Warning: 65 : BindingExpression (hash=53625532): Resolving source 
System.Windows.Data Warning: 67 : BindingExpression (hash=53625532): Framework mentor not found
System.Windows.Data Warning: 93 : BindingExpression (hash=53625532): Got InheritanceContextChanged event from EventToCommand (hash=37987662)
System.Windows.Data Warning: 65 : BindingExpression (hash=53625532): Resolving source 
System.Windows.Data Warning: 68 : BindingExpression (hash=53625532): Found data context element: ListView (hash=2475091) (OK)
System.Windows.Data Warning: 69 : BindingExpression (hash=53625532): DataContext is null
System.Windows.Data Warning: 65 : BindingExpression (hash=53625532): Resolving source 
System.Windows.Data Warning: 68 : BindingExpression (hash=53625532): Found data context element: ListView (hash=2475091) (OK)
System.Windows.Data Warning: 69 : BindingExpression (hash=53625532): DataContext is null
System.Windows.Data Warning: 65 : BindingExpression (hash=53625532): Resolving source 
System.Windows.Data Warning: 68 : BindingExpression (hash=53625532): Found data context element: ListView (hash=2475091) (OK)
System.Windows.Data Warning: 69 : BindingExpression (hash=53625532): DataContext is null
System.Windows.Data Warning: 65 : BindingExpression (hash=53625532): Resolving source 
System.Windows.Data Warning: 68 : BindingExpression (hash=53625532): Found data context element: ListView (hash=2475091) (OK)
System.Windows.Data Warning: 69 : BindingExpression (hash=53625532): DataContext is null
System.Windows.Data Warning: 65 : BindingExpression (hash=53625532): Resolving source 
System.Windows.Data Warning: 68 : BindingExpression (hash=53625532): Found data context element: ListView (hash=2475091) (OK)
System.Windows.Data Warning: 69 : BindingExpression (hash=53625532): DataContext is null
A first chance exception of type 'System.ArgumentException' occurred in System.ComponentModel.DataAnnotations.dll
System.Windows.Data Warning: 65 : BindingExpression (hash=53625532): Resolving source 
System.Windows.Data Warning: 68 : BindingExpression (hash=53625532): Found data context element: ListView (hash=2475091) (OK)
System.Windows.Data Warning: 76 : BindingExpression (hash=53625532): Activate with root item PresetManager (hash=65301425)
System.Windows.Data Warning: 106 : BindingExpression (hash=53625532):   At level 0 - for PresetManager.PresetSelectedCommand found accessor 
System.Windows.Data Error: 40 : BindingExpression path error: 'PresetSelectedCommand' property not found on 'object' ''PresetManager' (HashCode=65301425)'. BindingExpression:Path=PresetSelectedCommand; DataItem='PresetManager' (HashCode=65301425); target element is 'EventToCommand' (HashCode=37987662); target property is 'Command' (type 'ICommand')
System.Windows.Data Warning: 78 : BindingExpression (hash=53625532): TransferValue - got raw value {DependencyProperty.UnsetValue}
System.Windows.Data Warning: 86 : BindingExpression (hash=53625532): TransferValue - using fallback/default value 
System.Windows.Data Warning: 87 : BindingExpression (hash=53625532): TransferValue - using final value 

So if the command would be fired within the UserControl, I think I can hook up on it on my MainViewModel:

[InterestedIn(typeof(PresetManagerViewModel))]
public class MainViewModel : ViewModelBase
{
    protected override void OnViewModelCommandExecuted(IViewModel viewModel, ICatelCommand command, object commandParameter)
    {
        if (command != null && viewModel is PresetManagerViewModel) 
// how to get the correct command
{ var preset = commandParameter as Preset; FilterText = preset.Filter; } }

But how do I know which command was fired? There is no name property. I thought about using the Tag property of the Command instead, but I cannot set it. There could be more Commands on one ViewModel of course.

Another (more basic) question about the ViewModel of the control: I have this [Model] attribute on the PresetManager, and a corresponding constructor (see code above). But on the binding at the PresetManagerUserControl I directly use the PresetList property within the PresetManager without using either ViewModelToModel or Expose Attributes. How is this possible. I thought the VIewModel protects the model.

thanks again for any help,

Gorroux

Coordinator
Mar 27, 2012 at 9:20 AM

You can set the Tag of a command. The tag of a command must be set in the constructor. For example:

PresetSelectedCommand = new Command(OnPresetSelectedCommandExecute, null, "MyTag");
Mar 27, 2012 at 9:31 AM

Hello Geert,

the Command relaying now works with the Tag (if I fire it manually in the code), thanks.

This leaves firing it from the UI.

G.

Coordinator
Mar 27, 2012 at 9:32 AM

Can you try this:

<catel:EventToCommand 
               Command="{Binding ElementName=PresetList, Path=DataContext.PresetSelectedCommand, diag:PresentationTraceSources.TraceLevel=Medium}" 
               CommandParameter="{Binding Path=SelectedItem, ElementName=PresetList}" 
               DisableAssociatedObjectOnCannotExecute="False" />

Mar 27, 2012 at 9:57 AM

Still not working:

System.Windows.Data Error: 40 : BindingExpression path error: 'DataContext' property not found on 'object' ''PresetManager' (HashCode=29719599)'. BindingExpression:Path=DataContext.PresetSelectedCommand; DataItem='PresetManager' (HashCode=29719599); target element is 'EventToCommand' (HashCode=51169802); target property is 'Command' (type 'ICommand')

Which makes sense, as the PresetManager is a simple DataObjectBase<PresetManager>.

Coordinator
Mar 27, 2012 at 9:59 AM

How can the datacontext of the list be of type PresetManager? It should be PresetManagerViewModel. The view model *is* constructed (otherwise, you wouldn't have commands), so why is the datacontext set to the PresetManager? Is there another control wrapping the ListView?

Mar 27, 2012 at 10:19 AM

If I understand the first (long) trace correctly, the DataContext is null on the ListView as I just set the ItemsSource, and not the DataContext.

There is only a Grid around the ListView.

But thanks to your "DataContext." hint I found a solution:

<catel:EventToCommand Command="{Binding Path=DataContext.PresetSelectedCommand, 
                                        RelativeSource={RelativeSource FindAncestor, AncestorType=catel:UserControl, AncestorLevel=1}}" 
                        CommandParameter="{Binding Path=SelectedItem, ElementName=PresetList}" 
                        DisableAssociatedObjectOnCannotExecute="False" />

This now works :D

How about the effect that I can access properties within my Model without explicitely specifying. And how can I turn this off (actually I like it the way it is for this simple project, but on a more complex project I might want to protect the model).

THANKS

Mar 27, 2012 at 10:25 AM

Funny thing: I also use the PresetManagerViewModel (with an empty contructor) as DesignInstance. In this case, I no need an explicit property for the PresetList property.

Coordinator
Mar 27, 2012 at 10:54 AM

When you want to protected the model for real, you need to make it private on the view model and only expose the properties that are available for the view model.