schragnasher
Topic Author
Posts: 33
Joined: 07 Mar 2015, 01:58

Events in DataTemplates

30 Apr 2015, 04:05

I have a tough issue that I cant figure out how to solve in Unity.

I have an Items Control inside a data template. This is the simplified form
<DataTemplate DataType="{x:Type nodes:StatementNode}">
   <ItemsControl ItemsSource="{Binding DataContext.Responses, ElementName=StoryRoot}" 
      <ItemsControl.ItemTemplate>
         <DataTemplate>
            <Button Content="{Binding Text}" />
         </DataTemplate>
      </ItemsControl.ItemTemplate>
   </ItemsControl>
</DataTemplate>
They are displayed in a content presenter based on the Story node you are on, there are various nodes for different story GUI (Dialog Statements, Narration Views with Images etc.)
<ContentPresenter Content="{Binding CurrentNode}">
</ContentPresenter>
Now I need to change a property on the ViewModel when the user mouse overs the button. This is a dialog selection and I swap portraits based on the dialog you mouse over. (the options might be good evil etc. so I swap the portrait of the player)

I cannot attach to the button events in the code behind monobehavior as the buttons are created adhoc when I add responses to the items source. How can I respond to the MouseEnter event of these buttons?
 
User avatar
sfernandez
Site Admin
Posts: 1915
Joined: 22 Dec 2011, 19:20

Re: Events in DataTemplates

30 Apr 2015, 11:43

Hi,

What you need can be done with an attached property that you can use to update the ViewModel when it changes, for example:
public class AttachedProperties : DependencyObject
{
  public static DependencyProperty ButtonMouseOverProperty = DependencyProperty.RegisterAttached(
            "ButtonMouseOver", typeof(bool), typeof(AttachedProperties), new PropertyMetadata(false, OnButtonMouseOverChanged));

  public static bool GetButtonMouseOver(DependencyObject d)
  {
    return (bool)d.GetValue(ButtonMouseOverProperty);
  }

  public static void SetButtonMouseOver(DependencyObject d, bool value)
  {
    d.SetValue(ButtonMouseOverProperty, value);
  }

  private static void OnButtonMouseOverChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    Button button = d as Button;
    if (button != null)
    {
      MyViewModel vm = button.DataContext as MyViewModel;
      if (vm != null)
      {
        // Update ViewModel property
        if ((bool)e.NewValue) { // OVER=true }
        else { // OVER=false }
      }
    }
  }
}
<DataTemplate DataType="{x:Type nodes:StatementNode}">
   <ItemsControl ItemsSource="{Binding DataContext.Responses, ElementName=StoryRoot}" 
      <ItemsControl.ItemTemplate>
         <DataTemplate>
            <Button x:Name="btn" Content="{Binding Text}" />
            <DataTemplate.Triggers>
               <Trigger SourceName="btn" Property="IsMouseOver" Value="True">
                  <Setter TargetName="btn"
                     Property="local:AttachedProperties.ButtonMouseOver" Value="True"/>
               </Trigger>
            </DataTemplate.Triggers>
         </DataTemplate>
      </ItemsControl.ItemTemplate>
   </ItemsControl>
</DataTemplate> 
 
schragnasher
Topic Author
Posts: 33
Joined: 07 Mar 2015, 01:58

Re: Events in DataTemplates

02 May 2015, 04:40

So that is close to what i need, but not exactly. I found a tutorial that does exactly what i want, but i cant get the class to work correctly.
using UnityEngine;
using System.Collections;
#if UNITY_EDITOR
using Noesis;
#elif UNITY_STANDALONE_WIN
using Noesis;
#else
using System.Windows;
#endif
using System.Windows.Input;

    public class MouseEnterBehaviour : DependencyObject
    {
        public static DependencyProperty MouseOverCommandProperty = DependencyProperty.RegisterAttached(
            "MouseOverCommand",
            typeof(ICommand),
            typeof(MouseEnterBehaviour),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(MouseEnterBehaviour.MouseOverChanged)));

        public static DependencyProperty CommandPeramProperty = DependencyProperty.RegisterAttached(
            "CommandPeram",
            typeof(object),
            typeof(MouseEnterBehaviour),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(MouseEnterBehaviour.CommandPeramChanged)));

        public static void SetMouseOverCommand(DependencyObject target, ICommand value)
        {
            target.SetValue(MouseEnterBehaviour.MouseOverCommandProperty, value);
        }

        public static ICommand GetMouseOverCommand(DependencyObject target)
        {
            return (ICommand)target.GetValue(MouseEnterBehaviour.MouseOverCommandProperty);
        }

        private static void MouseOverChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            UIElement element = target as UIElement;
            if (element != null)
            {
                // If we're putting in a new command and there wasn't one already
                // hook the event
                if ((e.NewValue != null) && (e.OldValue == null))
                {
                    element.MouseEnter += element_MouseOver;
                }
                // If we're clearing the command and it wasn't already null
                // unhook the event
                else if ((e.NewValue == null) && (e.OldValue != null))
                {
                    element.MouseEnter -= element_MouseOver;
                }
            }
        }

        static void element_MouseOver(object sender, MouseEventArgs e)
        {
            UIElement element = (UIElement)sender;
            ICommand command = (ICommand)element.GetValue(MouseEnterBehaviour.MouseOverCommandProperty);
            command.Execute(element.GetValue(MouseEnterBehaviour.CommandPeramProperty));
        }

        public static void SetCommandPeram(DependencyObject target, object value)
        {
            target.SetValue(MouseEnterBehaviour.CommandPeramProperty, value);
        }

        public static object GetCommandPeram(DependencyObject target)
        {
            return (object)target.GetValue(MouseEnterBehaviour.CommandPeramProperty);
        }

        private static void CommandPeramChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            UIElement element = target as UIElement;
            if (element != null)
            {
                // If we're putting in a new command and there wasn't one already
                // hook the event
                if ((e.NewValue != null) && (e.OldValue == null))
                {
                    element.SetValue(MouseEnterBehaviour.CommandPeramProperty, e.NewValue);
                }
                // If we're clearing the command and it wasn't already null
                // unhook the event
                else if ((e.NewValue == null) && (e.OldValue != null))
                {
                    element.SetValue(MouseEnterBehaviour.CommandPeramProperty, e.NewValue);
                }
            }
        }
    }
Is there any reason why this might not work correctly? I cant get it to work at all, but iv used it no problem in a native WPF project to test.
 
schragnasher
Topic Author
Posts: 33
Joined: 07 Mar 2015, 01:58

Re: Events in DataTemplates

06 May 2015, 15:25

Gonna give this a bump as I'm still having issues. There are no errors when using this class on my buttons. It just does nothing. It should be calling my command after I mouse over.
 
User avatar
sfernandez
Site Admin
Posts: 1915
Joined: 22 Dec 2011, 19:20

Re: Events in DataTemplates

06 May 2015, 16:26

I've been doing some tests just this morning but I was unable to reproduce the problem. Maybe my sample does not reflect the same scenario:
<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ItemsControl ItemsSource="{Binding Responses}" Width="200" Height="200">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <Button Content="{Binding Text}" MinWidth="100" MinHeight="20"
                        MouseEnterBehaviour.MouseOverCommand="{Binding MouseOver}" MouseEnterBehaviour.MouseOverParam="Hey!"/>
                    <TextBlock Text="{Binding Over}" Margin="2,0,0,0" MinWidth="100" Background="DimGray"/>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid> 
public class MouseEnterBehaviour : DependencyObject
{
    public static DependencyProperty MouseOverCommandProperty = DependencyProperty.RegisterAttached(
        "MouseOverCommand", typeof(ICommand), typeof(MouseEnterBehaviour),
        new FrameworkPropertyMetadata(null, new PropertyChangedCallback(MouseOverChanged)));

    public static void SetMouseOverCommand(DependencyObject target, ICommand value)
    {
        target.SetValue(MouseEnterBehaviour.MouseOverCommandProperty, value);
    }

    public static ICommand GetMouseOverCommand(DependencyObject target)
    {
        return (ICommand)target.GetValue(MouseEnterBehaviour.MouseOverCommandProperty);
    }

    private static void MouseOverChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        UIElement element = target as UIElement;
        if (element != null)
        {
            if ((e.NewValue != null) && (e.OldValue == null))
            {
                Debug.Log("Registered to MouseEnter");
                element.MouseEnter += element_MouseOver;
            }
            else if ((e.NewValue == null) && (e.OldValue != null))
            {
                Debug.Log("Unregistered to MouseEnter");
                element.MouseEnter -= element_MouseOver;
            }
        }
    }

    public static DependencyProperty MouseOverParamProperty = DependencyProperty.RegisterAttached(
        "MouseOverParam", typeof(string), typeof(MouseEnterBehaviour),
        new FrameworkPropertyMetadata(string.Empty));

    public static void SetMouseOverParam(DependencyObject target, string value)
    {
        target.SetValue(MouseEnterBehaviour.MouseOverParamProperty, value);
    }

    public static string GetMouseOverParam(DependencyObject target)
    {
        return (string)target.GetValue(MouseEnterBehaviour.MouseOverParamProperty);
    }

    static void element_MouseOver(object sender, MouseEventArgs e)
    {
        UIElement element = (UIElement)sender;
        ICommand command = GetMouseOverCommand(element);
        object param = GetMouseOverParam(element);
        command.Execute(param);
    }
}

public class ItemVM: Noesis.Samples.NotifyPropertyChangedBase
{
    public string Text { get; set; }
    public string Over { get; private set; }
    public ICommand MouseOver { get; private set; }
    public ItemVM()
    {
        MouseOver = new Noesis.Samples.DelegateCommand(OnMouseOver);
    }

    public void OnMouseOver(object param)
    {
        Over = (string)param;
        OnPropertyChanged("Over");

        Debug.Log("OnMouseOver called with param = " + Over);
    }
}

public class PanelVM
{
    public List<ItemVM> Responses { get; private set; }
    public PanelVM() { Responses = new List<ItemVM>(); }
} 
public class Test : MonoBehaviour
{
    void Start()
    {
        var gui = GetComponent<NoesisGUIPanel>();
        var content = gui.GetContent();

        PanelVM vm = new PanelVM();

        vm.Responses.Add(new ItemVM { Text = "Ok" });
        vm.Responses.Add(new ItemVM { Text = "Maybe" });
        vm.Responses.Add(new ItemVM { Text = "Not at all" });
        vm.Responses.Add(new ItemVM { Text = "Bye" });

        content.DataContext = vm;
    }
} 
You should add some log messages as I did to check what part is failing (hook to the MouseEnter event, execution of the command, view model property change...).

But it will be better if you can create a ticket in our bugtracker and attach your project, so I can debug it to see what is happening.
 
schragnasher
Topic Author
Posts: 33
Joined: 07 Mar 2015, 01:58

Re: Events in DataTemplates

06 May 2015, 17:14

Thank you. I can't check now but I'll see if I can get your example working. Appreciate the effort. If you can do it then it must be on my end.
 
schragnasher
Topic Author
Posts: 33
Joined: 07 Mar 2015, 01:58

Re: Events in DataTemplates

06 May 2015, 22:58

Ok thank you for this it helpedmy main issue was
new FrameworkPropertyMetadata(new PropertyChangedCallback(MouseOverChanged)));
Needed to be ...
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(MouseOverChanged)));
The callbacks were not setup correctly.

So i am getting proper response from the buttons. There is a casting issue though as i was hoping to make it generic by using object as the param type, this might be worth a bug ticket though so ill post it that way.
 
User avatar
sfernandez
Site Admin
Posts: 1915
Joined: 22 Dec 2011, 19:20

Re: Events in DataTemplates

07 May 2015, 19:03

Nice to hear it finally worked :)
Anyway, I think we need to show some message when creating the metadata that way. Or don't allow it at all.

About the param, what problem are you getting when using an object type?

Who is online

Users browsing this forum: No registered users and 1 guest