none
How to Handle events from a Usercontrol used as a ItemTemplate?

    Question

  • Hi, I am just looking/learning about using Binding to a LIstBox, and using a DataTemplate to have a UserControl as the items in the listbox.

    <ListBox >
        <ItemsControl.ItemTemplate >
       <DataTemplate>    
        <!—This is a Usercontrol we want to load in the Listbox-->
        <TestUserControls:UserControl2></TestUserControls:UserControl2>
       </DataTemplate>
      </ItemsControl.ItemTemplate>
    </ListBox>
    

    What I want to know how to do, is be able to handle any events from UserControls used as Items from a class outside of the UserControl itself.

    For example, assume the user control has a number of buttons. Within the UseControl code behind, I can easily handle the Click event for each button, but I then want to be able to raise my own event, passing up which button was clicked (just via some Id I can use to identify the button), and of course which User control it is from (again from some Id I will have within the control)

    Because I am not creating instances of the UserControl, and then adding to whatever container manually, I do not have any direct access to each UserControl "instance" so I could then directly handle such events.

    Can anyone shed any light on the "Proper" WPF way of doing such a thing (which I thought would be quite a common thing to want to do)

    Thanks in advance for any help, regards Peter

     

     

    Thursday, May 06, 2010 2:21 AM

Answers

  • I would definately look into the MVVM pattern if I were you. Josh Smith's implementation is pretty easy to follow and very useful. I've included his RelayCommand class here, because it's ridiculously useful.

     

    Also, I know I haven't used a UserControl here, but the concept will work just the same. I wouldn't use events like I have here if I could help it but since you asked... ;) Commanding is a much more solid WPF/MVVM practice.

     

    XAML

     

    <ListView ItemsSource="{Binding AllClients}">

        <ListView.View>

            <GridView>

                <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"></GridViewColumn>

                <GridViewColumn Header="Age" DisplayMemberBinding="{Binding Age}"></GridViewColumn>

                <GridViewColumn Header="Button">

                    <GridViewColumn.CellTemplate>

                        <DataTemplate>

                            <Button Content="Click Me" Command="{Binding Button1Command}" CommandParameter="{Binding }"></Button>

                        </DataTemplate>

                    </GridViewColumn.CellTemplate>

                </GridViewColumn>

            </GridView>

        </ListView.View>

    </ListView>


    CODE
        using System.ComponentModel;
        using System.Collections.ObjectModel;
        public partial class Window1 : Window
        {
            public ObservableCollection<ClientViewModel> AllClients { get; set; } // Public property so XAML use it for bindings

            public Window1()
            {
                InitializeComponent();

                AllClients = new ObservableCollection<ClientViewModel>() // Setup the collection
                {
                    new ClientViewModel() { Age = 20, Name = "Peter-j-c"},
                    new ClientViewModel() { Age = 21, Name = "MSearles"},                
                };

                AllClients.ToList().ForEach(c => c.Button1Clicked += new EventHandler<ButtonClickEventArgs>(c_Button1Clicked)); // Listen to the Button1Clicked event from each ViewModel
                this.DataContext = this; // Set this Windows DataContext to itself, again so the XAML knows where to look for the properties it's binding to
            }

    // What do we do when the Button1Clicked event is fired from the ClientViewModel
            void c_Button1Clicked(object sender, ButtonClickEventArgs e)
            {
                System.Windows.Forms.MessageBox.Show("From Event : " + e.Client.Name);
            }             
        }

        
        // So we can pass which ClientViewModel raises the Click event
        public class ButtonClickEventArgs : EventArgs 
        {
            public ButtonClickEventArgs(ClientViewModel client) 
            {
                Client = client;
            }

            public ClientViewModel Client {get; set;}
        }

        public class ClientViewModel
        {
            public string Name { get; set; }
            public int Age { get; set; }

            public event EventHandler<ButtonClickEventArgs> Button1Clicked; // An easy way to create an event
            public ICommand Button1Command { get; set; } // This is the command our button will execute when it's clicked
            
            public ClientViewModel()
            {
                Button1Command = new RelayCommand(param => this.ShowName(param)); // Create the command
            }

            private void ShowName(object param) // This is what Button1Command will execute 
            {
                ClientViewModel client = param as ClientViewModel; // Convert object to a ClientViewModel

                System.Windows.Forms.MessageBox.Show("From ViewModel : " + client.Name); // Show the name that was clicked

                if (Button1Clicked != null) Button1Clicked(this, new ButtonClickEventArgs(this)); // And raise an event (if required) - just a demo really, you would probably avoid this in practice
            }
        }

        // Josh Smith's RelayCommand class
        public class RelayCommand : ICommand
        {
            #region Fields

            readonly Action<object> _execute;
            readonly Predicate<object> _canExecute;

            #endregion // Fields

            #region Constructors

            /// <summary>
            /// Creates a new command that can always execute.
            /// </summary>
            /// <param name="execute">The execution logic.</param>
            public RelayCommand(Action<object> execute)
                : this(execute, null)
            {
            }

            /// <summary>
            /// Creates a new command.
            /// </summary>
            /// <param name="execute">The execution logic.</param>
            /// <param name="canExecute">The execution status logic.</param>
            public RelayCommand(Action<object> execute, Predicate<object> canExecute)
            {
                if (execute == null)
                    throw new ArgumentNullException("execute");

                _execute = execute;
                _canExecute = canExecute;
            }

            #endregion // Constructors

            #region ICommand Members

            public bool CanExecute(object parameter)
            {
                return _canExecute == null ? true : _canExecute(parameter);
            }

            public event EventHandler CanExecuteChanged
            {
                add { CommandManager.RequerySuggested += value; }
                remove { CommandManager.RequerySuggested -= value; }
            }

            public void Execute(object parameter)
            {
                _execute(parameter);
            }

            #endregion // ICommand Members
        }
    }

    Warm regards,
    Matt

     

    • Proposed as answer by Matt Searles Thursday, May 06, 2010 6:28 AM
    • Marked as answer by Peter-j-c Thursday, May 06, 2010 7:46 AM
    Thursday, May 06, 2010 3:42 AM

All replies

  • I would definately look into the MVVM pattern if I were you. Josh Smith's implementation is pretty easy to follow and very useful. I've included his RelayCommand class here, because it's ridiculously useful.

     

    Also, I know I haven't used a UserControl here, but the concept will work just the same. I wouldn't use events like I have here if I could help it but since you asked... ;) Commanding is a much more solid WPF/MVVM practice.

     

    XAML

     

    <ListView ItemsSource="{Binding AllClients}">

        <ListView.View>

            <GridView>

                <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"></GridViewColumn>

                <GridViewColumn Header="Age" DisplayMemberBinding="{Binding Age}"></GridViewColumn>

                <GridViewColumn Header="Button">

                    <GridViewColumn.CellTemplate>

                        <DataTemplate>

                            <Button Content="Click Me" Command="{Binding Button1Command}" CommandParameter="{Binding }"></Button>

                        </DataTemplate>

                    </GridViewColumn.CellTemplate>

                </GridViewColumn>

            </GridView>

        </ListView.View>

    </ListView>


    CODE
        using System.ComponentModel;
        using System.Collections.ObjectModel;
        public partial class Window1 : Window
        {
            public ObservableCollection<ClientViewModel> AllClients { get; set; } // Public property so XAML use it for bindings

            public Window1()
            {
                InitializeComponent();

                AllClients = new ObservableCollection<ClientViewModel>() // Setup the collection
                {
                    new ClientViewModel() { Age = 20, Name = "Peter-j-c"},
                    new ClientViewModel() { Age = 21, Name = "MSearles"},                
                };

                AllClients.ToList().ForEach(c => c.Button1Clicked += new EventHandler<ButtonClickEventArgs>(c_Button1Clicked)); // Listen to the Button1Clicked event from each ViewModel
                this.DataContext = this; // Set this Windows DataContext to itself, again so the XAML knows where to look for the properties it's binding to
            }

    // What do we do when the Button1Clicked event is fired from the ClientViewModel
            void c_Button1Clicked(object sender, ButtonClickEventArgs e)
            {
                System.Windows.Forms.MessageBox.Show("From Event : " + e.Client.Name);
            }             
        }

        
        // So we can pass which ClientViewModel raises the Click event
        public class ButtonClickEventArgs : EventArgs 
        {
            public ButtonClickEventArgs(ClientViewModel client) 
            {
                Client = client;
            }

            public ClientViewModel Client {get; set;}
        }

        public class ClientViewModel
        {
            public string Name { get; set; }
            public int Age { get; set; }

            public event EventHandler<ButtonClickEventArgs> Button1Clicked; // An easy way to create an event
            public ICommand Button1Command { get; set; } // This is the command our button will execute when it's clicked
            
            public ClientViewModel()
            {
                Button1Command = new RelayCommand(param => this.ShowName(param)); // Create the command
            }

            private void ShowName(object param) // This is what Button1Command will execute 
            {
                ClientViewModel client = param as ClientViewModel; // Convert object to a ClientViewModel

                System.Windows.Forms.MessageBox.Show("From ViewModel : " + client.Name); // Show the name that was clicked

                if (Button1Clicked != null) Button1Clicked(this, new ButtonClickEventArgs(this)); // And raise an event (if required) - just a demo really, you would probably avoid this in practice
            }
        }

        // Josh Smith's RelayCommand class
        public class RelayCommand : ICommand
        {
            #region Fields

            readonly Action<object> _execute;
            readonly Predicate<object> _canExecute;

            #endregion // Fields

            #region Constructors

            /// <summary>
            /// Creates a new command that can always execute.
            /// </summary>
            /// <param name="execute">The execution logic.</param>
            public RelayCommand(Action<object> execute)
                : this(execute, null)
            {
            }

            /// <summary>
            /// Creates a new command.
            /// </summary>
            /// <param name="execute">The execution logic.</param>
            /// <param name="canExecute">The execution status logic.</param>
            public RelayCommand(Action<object> execute, Predicate<object> canExecute)
            {
                if (execute == null)
                    throw new ArgumentNullException("execute");

                _execute = execute;
                _canExecute = canExecute;
            }

            #endregion // Constructors

            #region ICommand Members

            public bool CanExecute(object parameter)
            {
                return _canExecute == null ? true : _canExecute(parameter);
            }

            public event EventHandler CanExecuteChanged
            {
                add { CommandManager.RequerySuggested += value; }
                remove { CommandManager.RequerySuggested -= value; }
            }

            public void Execute(object parameter)
            {
                _execute(parameter);
            }

            #endregion // ICommand Members
        }
    }

    Warm regards,
    Matt

     

    • Proposed as answer by Matt Searles Thursday, May 06, 2010 6:28 AM
    • Marked as answer by Peter-j-c Thursday, May 06, 2010 7:46 AM
    Thursday, May 06, 2010 3:42 AM
  • Thankyou very much Matt, I will study this, and check out that MVVM pattern article by Josh Smith.

    regards Peter

    Thursday, May 06, 2010 6:17 AM
  • Welcome. Just post here if you run into any issues.

     

    Matt

    Thursday, May 06, 2010 6:29 AM
  • Hi again Matt. Once again thanks heaps for the above input; this certainly does exactly what I am after for the button clicks. My only problem now is how to handle some other events from the UserControl, for example, Double click or Right Click on the User Control itself.

    From what I can see, we can use the Command on the Button click since the Command sent form the button is infact the click event, hence we can bind the command to a ICommand property in our "ViewModel" that is set as the User controls DataContent.

    So, from what I understand from what I have read so far, is that we can use these Commands if the UIElement has a command for a certain action we are interested in (eg as we have for the Button click).

    I went on to read about Routed Events, and found that I can capture these events from, for example, a Window that contains our collection of UserControls, for example I had the below in my window

    <Window x:Class="TestUserControls.Window2"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:TestUserControls="clr-namespace:TestUserControls"  
     
      
      Title="Window2" Height="300" Width="300" Loaded="Window_Loaded" 
        Control.MouseDoubleClick="Window_MouseDoubleClick" 
         Mouse.PreviewMouseDown="Window_PreviewMouseDown"
         Mouse.MouseDown="Window_MouseDown" ButtonBase.Click ="CommonClickHandler">

    For some reason I never got the MouseDown, but DID get the PreviewMouseDown.

    The disappointing thing here is the need for the "Code Behind" handlers for the events in the actual Window, instead of being able to have it in the "ViewModel" class.

    Is the above (using Routed Events) the correct "WPF" way of handling events that don't appear to be available via Commands, or is there some way other (Better) way of doing this (ie is there still a way of doing it via Commands)

    Also, which ever method, is there a way so that, like the command, the "C# Code" can be in the "ViewModel" class as opposed to needing the Code behind in one of the UI classes.

    Once again, thanks in advance for any futher information

    regards, Peter

     

    Friday, May 07, 2010 3:49 AM
  • Personally, for stuff like that, I'll generally just use a little code behind, however, a purist will tell you its a big no no. It also depends on a lot on what you want to achieve with the event handler, because you might be able to do it in XAML with a Trigger. That said, I had a need to execute a command on a ListViewItem DoubleClick... and here is the solution I use.

    http://www.codeproject.com/Articles/42111/Selector-DoubleClick-Behaviour-calling-ViewModel-I.aspx

     

    I'm pretty sure without a huge amount of effort you could adapt that code to execute a command for any type of Event you wanted, though it might be worth posting this part of the question to a new thread.

     

    Warm regards,

    Matt

    Friday, May 07, 2010 5:19 AM
  • Thanks again Matt, I checked out the link, and that does show how how I could use commands to do what I am after.

    Seems to be a bit of code behind to do to declare these Commands, which is fine if that is the only way to do this. Might see if there are any other comments on how to handle "any" event (not just button clicks) from a bound UserControl (while I keep searching myslef)

    best regards

    Peter

    Friday, May 07, 2010 7:36 AM
  • I also think that a little code behind is harmless, also it is the only (feasible) way if you need to access event args. And if the code-behind does not know the type of the view-model (i.e. the viewmodel is injected in some way), reflection could be used to call a method (or command) from code behind. Just like:

    DataContext.GetType().GetMethod("SomeMethod").Invoke(eventArgs.GetPosition(this));
    Friday, May 07, 2010 10:19 AM
  • Cool no worries Ugur, yes I think I will just handle the events with code behind handlers in the user control, and then I should be able to just call any methods (or raise normal C# events on) the ViewModel class (that is accessible via the DataContext)

    I saw some suggestions for custom commands, but it ends up being a lot more code you need then just the little bit you need to juts have the code behind for the handlers.

    Saturday, May 08, 2010 8:05 AM
  • Hi all, I thought I would share something I just found, that I think is at least, going to solve what I am after.

    See the link http://tomlev2.wordpress.com/2009/10/26/vs2010-binding-support-in-inputbindings, for full details, but it looks like we can now use  MouseBinding to hookup to a comand in .net 4

    So for me, I can do the following...

    <UserControl.InputBindings>
      <MouseBinding Gesture="RightClick" Command="{Binding TestClick}"/>
      <MouseBinding Gesture="LeftDoubleClick" Command="{Binding DoubleClick}"/>
     </UserControl.InputBindings>

    Where my RightClick and DoubleClick are comands on the ViewModel 

    I tried this, (in a eval Express version of Dev studio 2010,) and it appears to work fine. Only thing is need latest dev studio to do this

     

    Sunday, May 09, 2010 5:06 AM