- schragnasher
- Posts: 33
- Joined:
Events in DataTemplates
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
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.)
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?
I have an Items Control inside a data template. This is the simplified form
Code: Select all
<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>
Code: Select all
<ContentPresenter Content="{Binding CurrentNode}">
</ContentPresenter>
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?
-
sfernandez
Site Admin
- Posts: 2984
- Joined:
Re: Events in DataTemplates
Hi,
What you need can be done with an attached property that you can use to update the ViewModel when it changes, for example:
What you need can be done with an attached property that you can use to update the ViewModel when it changes, for example:
Code: Select all
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 }
}
}
}
}
Code: Select all
<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
- Posts: 33
- Joined:
Re: Events in DataTemplates
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.
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.
Code: Select all
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);
}
}
}
}
- schragnasher
- Posts: 33
- Joined:
Re: Events in DataTemplates
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.
-
sfernandez
Site Admin
- Posts: 2984
- Joined:
Re: Events in DataTemplates
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:
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.
Code: Select all
<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>
Code: Select all
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>(); }
}
Code: Select all
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;
}
}
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
- Posts: 33
- Joined:
Re: Events in DataTemplates
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
- Posts: 33
- Joined:
Re: Events in DataTemplates
Ok thank you for this it helpedmy main issue was
Needed to be ...
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.
Code: Select all
new FrameworkPropertyMetadata(new PropertyChangedCallback(MouseOverChanged)));
Code: Select all
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(MouseOverChanged)));
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.
-
sfernandez
Site Admin
- Posts: 2984
- Joined:
Re: Events in DataTemplates
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?
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: Ahrefs [Bot], Semrush [Bot] and 71 guests