(WPF4+ Prism4) How to use InvokeCommandAction ?

Topics: Prism v4 - WPF 4
May 27, 2013 at 5:46 AM
Edited May 27, 2013 at 5:48 AM
I have a TreeView with HierarchicalDataTemplate. In the leaf node of the treeview, I have a button
binding to a command object, which have done the job as I expected.

Here is the working Xaml code:

<UserControl x:Class="YhControls.Views.ListFilesView"
         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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
         xmlns:m="clr-namespace:YhCommon;assembly=YhCommon"
         xmlns:vm="clr-namespace:YhControls.ViewModels"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid Name="root">
    <Grid.Resources>
        <HierarchicalDataTemplate DataType = "{x:Type vm:Node}"  ItemsSource = "{Binding Path=nodes}">
            <TextBlock Text="{Binding Path=Name}" />
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate DataType = "{x:Type vm:Leaf}"  ItemsSource = "{Binding Path=nodes}" >
            <Button Content="{Binding Path=Name}" Command="{Binding ElementName=root,Path=DataContext.ShowFileCommand}" CommandParameter="{Binding }" />

        </HierarchicalDataTemplate>
    </Grid.Resources>
.....
</UserControl>

Now I want change the implementation from "Binding Commands" to "InvokeCommandAction".

Here is the version of "InvokeCommandAction":

<UserControl x:Class="YhControls.Views.ListFilesView"
         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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
         xmlns:m="clr-namespace:YhCommon;assembly=YhCommon"
         xmlns:vm="clr-namespace:YhControls.ViewModels"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid Name="root">
    <Grid.Resources>
        <HierarchicalDataTemplate DataType = "{x:Type vm:Node}"  ItemsSource = "{Binding Path=nodes}">
            <TextBlock Text="{Binding Path=Name}" />
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate DataType = "{x:Type vm:Leaf}"  ItemsSource = "{Binding Path=nodes}" >
            <Button Content="{Binding Path=Name}"  >
                    <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click" >
                        <i:InvokeCommandAction Command="{Binding ElementName=root,Path= DataContext.ShowFileCommand}" CommandParameter="{Binding }"  />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
        </HierarchicalDataTemplate>

    </Grid.Resources>
...
</UserControl>

But, in "InvokeCommandAction" version, I got exception:
"Baml stream has unexpected record. Try to add to InvokeCommandAction,
but it is not a collection or which has TypeConverter."

Hope some one can help me.
May 27, 2013 at 5:44 PM
Hi,

Based on your code snippets, I don't see what could be the root of your problem that is causing that exception. It would be helpful if you could provide more details about what you are trying to achieve and why you need to use InvokeCommandAction instead of simply binding the button to a command. Also, it would be useful if you post the entire exception message so we can dig a little further on the problem.

Regards,

Federico Martinez
http://blogs.southworks.net/fmartinez
May 28, 2013 at 2:22 AM
fmartinez,

Sorry, I cannot rebuild the senario.

But I guess that may be there are some incompatibility between Nuget packages Blend.Interactivity.WPF.v4.0 and Blend.Interactivity.WPF.

In my solution, some projects reference Nuget package Blend.Interactivity.WPF.v4.0 and some use Blend.Interactivity.WPF and some use both. I guess it IS the source of
problem. After I only reference Blend.Interactivity.WPF.v4.0, the program is OK now and I do not know how to rebuild the scene.

Install Blend.Interactivity.WPF.v4.0, Blend.Interactivity.WPF and uninstall them in different sequences will show some Phenomenon strange to me.

Why I want use InvokeCommandAction?
  1. After I replace Button with TextBlock, Content with Text, and Click with MouseDown, every thing is OK as expected.
  2. When program is running, if I press arrow key DOWN, the selected treeview leaf changed. I try to use this feature to raise event. (because use arrow key is more convenient than mouse in my project).
  3. I modify TextBlock with property Focusable="True" and replace MouseDown with GotFocus, but this DO NOT work. I am still trying to solve this problem.
Hope you can help me.
Thanks
May 28, 2013 at 6:08 PM
Hi,

I'm glad you could solve your original problem.

Regarding your second problem, it would be helpful if you could provide us a sample of your application so we can help you implement the functionality you are describing in an InvokeCommandAction.

Regards,

Federico Martinez
http://blogs.southworks.net/fmartinez
May 30, 2013 at 7:24 AM
Edited May 30, 2013 at 7:36 AM
Federico,

My Goal:
Implement a WPF TreeView, it will response when user browse to any leaf TreeViewItem of the TreeView.

My Project QTVB compose of following tree files.

(1) MainWindow.xaml
<Window x:Class="QTVB.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <Button Content="Setup" DockPanel.Dock="Top" Command="{Binding SetupCommand}" />
        <TextBox Text="{Binding Message}" DockPanel.Dock="Top"/>
        <TreeView Name="root" ItemsSource="{Binding Leafs}">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource = "{Binding Path=nodes}">
                    <TextBlock Text="{Binding Path=Name}" >
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="MouseDown" >
                                <i:InvokeCommandAction Command="{Binding ElementName=root,Path= DataContext.FooCommand}" CommandParameter="{Binding }"  />
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </TextBlock>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </DockPanel>
</Window>
(2) MainWindow.xaml.cs
namespace QTVB
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainViewModel();
        }
    }
}
(3) MainViewModel.cs
namespace QTVB
{
    public class MainViewModel:NotificationObject
    {
        public const string LeafsPropertyName = "Leafs";
        private ObservableCollection<Leaf> _Leafs = new ObservableCollection<Leaf>();
        public ObservableCollection<Leaf> Leafs
        {
            get
            {
                return _Leafs;
            }
            set
            {
                if (_Leafs == value)
                {
                    return;
                }
                _Leafs = value;
                RaisePropertyChanged(LeafsPropertyName);
            }
        }
        public const string MessagePropertyName = "Message";
        private string _Message = string.Empty;
        public string Message
        {
            get
            {
                return _Message;
            }
            set
            {
                if (_Message == value)
                {
                    return;
                }
                _Message = value;
                RaisePropertyChanged(MessagePropertyName);
            }
        }
        private DelegateCommand<Leaf> _FooCommand;
        public DelegateCommand<Leaf> FooCommand
        {
            get
            {
                return _FooCommand
                    ?? (_FooCommand = new DelegateCommand<Leaf>(
                                          (p) =>
                                          {
                                              if (p.IsLeaf) Message = p.Name;
                                          }));
            }
        }
        private DelegateCommand _SetupCommand;
        public DelegateCommand SetupCommand
        {
            get
            {
                return _SetupCommand
                    ?? (_SetupCommand = new DelegateCommand(
                                          () =>
                                          {
                                              ConstructLeafs();
                                          }));
            }
        }
        public MainViewModel()
        {
            ConstructLeafs();
        }
        public void ConstructLeafs()
        {
            Leafs.Clear();
            Leafs.Add(new Leaf("Stem1", false));
            Leafs.Add(new Leaf("Stem2", false));
            Leafs[0].nodes.Add(new Leaf("leaf11", true));
            Leafs[0].nodes.Add(new Leaf("leaf12", true));
            Leafs[0].nodes.Add(new Leaf("leaf13", true));
            Leafs[1].nodes.Add(new Leaf("leaf21", true));
            Leafs[1].nodes.Add(new Leaf("leaf22", true));
        }
    }
    public class Leaf : NotificationObject
    {
        public const string NamePropertyName = "Name";
        private string _Name = string.Empty;
        public string Name
        {
            get
            {
                return _Name;
            }
            set
            {
                if (_Name == value)
                {
                    return;
                }
                _Name = value;
                RaisePropertyChanged(NamePropertyName);
            }
        }
        public const string IsLeafPropertyName = "IsLeaf";
        private bool _IsLeaf = false;
        public bool IsLeaf
        {
            get
            {
                return _IsLeaf;
            }
            set
            {
                if (_IsLeaf == value)
                {
                    return;
                }
                _IsLeaf = value;
                RaisePropertyChanged(IsLeafPropertyName);
            }
        }
        public ObservableCollection<Leaf> nodes
        {
            get;
            set;
        }
        public Leaf(string name,bool isleaf)
        {
            Name = name;
            IsLeaf = isleaf;
            nodes = new ObservableCollection<Leaf>();
        }
    }
}
In this project QTVB , user can click at the leaf TreeViewItem to let FooCommand execute as expected. But when user use arrow key to navigate among leaf TreeViewItem, nothing happens.

So I create project QTVB2. In QTVB2, the only difference is the file MainWindow.xaml.
(1) MainWindow.xaml
<Window x:Class="QTVB.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <Button Content="Setup" DockPanel.Dock="Top" Command="{Binding SetupCommand}" />
        <TextBox Text="{Binding Message}" DockPanel.Dock="Top"/>
        <TreeView Name="root" ItemsSource="{Binding Leafs}" >
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="SelectedItemChanged" >
                    <i:InvokeCommandAction Command="{Binding FooCommand}" CommandParameter="{Binding ElementName=root, Path=SelectedValue }"  />
                </i:EventTrigger>
            </i:Interaction.Triggers>
           <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource = "{Binding Path=nodes}">
                    <TextBlock Text="{Binding Path=Name}" >
                    </TextBlock>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </DockPanel>
</Window>
In project QTVB2, when user browse to leaf TreeViewItem using arrow key, FooCommand execute as expected.
But after browse to some leaf, if user click Setup button to execute SetupCommand, exception "can not convert object of type System.Windows.Controls.TreeViewItem to type QTVB.Leaf" throws
How to Fix it ? Thanks.

Best Regards
HJY
May 30, 2013 at 6:22 PM
Hi,

I created a sample using the code you provided and it seems to work fine for me. I believe that your problem could be related to something else that is not in the code you provided. Nevertheless, I uploaded the sample to my SkyDrive account so you can use it.

Regards,

Federico Martinez
http://blogs.southworks.net/fmartinez
Jun 3, 2013 at 6:40 AM
Federico,
  1. I use my own code in another computer, It DO work. So it is true as you said "your problem could be related to something else that is not in the code you provided".
  2. (a)
    I reinstalled VS 2010 in my computer, recompiled the project, it still DID NOT work.
    (b)
    I installed VS 2012 in my computer, just executed the binary produced in (a), It DID work.
  3. I still do not understand what is going wrong in my computer.
Best Regards
HJY
Jun 3, 2013 at 7:10 PM
Hi,

When I created the sample using your code, I used Visual Studio 2012 to do it. Nevertheless, I recreated the sample in Visual Studio 2010 and it is also working for me. Therefore, you probably have an issue with your Visual Studio 2010 installation or your computer environment.

Anyway, I'm glad that your solution is now working.

Regards,

Federico Martinez
http://blogs.southworks.net/fmartinez