I've created a WPF window. In that I've created style for a textbox which have size information as well as some eventsetters; Then, I've created some textboxes assigning the above style.
Now for one case I need to remove events of that textbox. But I cant do that.
Even though I do the following the event is not detached. It still there.
txt9.PreviewLostKeyboardFocus -= txt9_PreviewLostKeyboardFocus;
This occurs only when the event is attached in the style itself.
If it were within the TextBox control the event detaches fine.
My Code:
XAML:
<Window.Resources>
<ResourceDictionary>
<Style x:Key="txtStyle11" TargetType="TextBox">
<Setter Property="Width" Value="150"/>
<Setter Property="Height" Value="35"/>
<EventSetter Event="PreviewLostKeyboardFocus" Handler="txt9_PreviewLostKeyboardFocus"/>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid MinWidth="50">
<TextBox Style="{StaticResource txtStyle11}" x:Name="txt9"/>
</Grid>
CS:
public MainWindow()
{
txt9.PreviewLostKeyboardFocus -= txt9_PreviewLostKeyboardFocus;
}
Hi we cannot Unsubscribe events whcih are subscribed in EventSetter, because
internally EventSetter class becomes immutable and so the properties of the
EventSetter object cannot be modified. Below is the sample which I tried to
illustrate the same. This sample throws exception when we try to change the
property of setterbase class
public partial class MainWindow : Window
{
KeyboardFocusChangedEventHandler mydelegate, emptydelegate;
EventSetter eventSetter;
public MainWindow()
{
InitializeComponent();
emptydelegate = delegate(object sender, KeyboardFocusChangedEventArgs e)
{
};
mydelegate = delegate(object sender, KeyboardFocusChangedEventArgs e)
{
MessageBox.Show("My Own Event is still Unsubscribed");
};
eventSetter = new EventSetter();
eventSetter.Event = TextBox.PreviewLostKeyboardFocusEvent;
eventSetter.Handler = mydelegate;
Style myStyle = new System.Windows.Style(txt9.GetType());
myStyle.Setters.Add(eventSetter);
txt9.Style = myStyle;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
eventSetter.Handler= emptydelegate;
}
}
Related
I have a usercontrol that has a scrollviewer, then a bunch of child controls like text boxes, radio buttons, and listboxes, etc inside of it. I can use the mouse wheel to scroll the parent scrollviewer until my mouse lands inside a listbox then, the mouse wheel events start going to the listbox.
Is there any way to have the listbox send those events back up to the parent control? Removing the listbox from within side the parent control like this question suggests (Mouse wheel not working when over ScrollViewer's child controls) isnt a solution.
I have tried
private void ListBox_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
}
but that didnt work either.
Thanks
This can be accomplished via attached behaviors.
http://josheinstein.com/blog/index.php/2010/08/wpf-nested-scrollviewer-listbox-scrolling/
Edit:
Here is the linked solution:
"So instead I came up with the following IgnoreMouseWheelBehavior. Technically it’s not ignoring the MouseWheel, but it is “forwarding” the event back up and out of the ListBox. Check it."
/// <summary>
/// Captures and eats MouseWheel events so that a nested ListBox does not
/// prevent an outer scrollable control from scrolling.
/// </summary>
public sealed class IgnoreMouseWheelBehavior : Behavior<UIElement>
{
protected override void OnAttached( )
{
base.OnAttached( );
AssociatedObject.PreviewMouseWheel += AssociatedObject_PreviewMouseWheel ;
}
protected override void OnDetaching( )
{
AssociatedObject.PreviewMouseWheel -= AssociatedObject_PreviewMouseWheel;
base.OnDetaching( );
}
void AssociatedObject_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
var e2 = new MouseWheelEventArgs(e.MouseDevice,e.Timestamp,e.Delta);
e2.RoutedEvent = UIElement.MouseWheelEvent;
AssociatedObject.RaiseEvent(e2);
}
}
And here’s how you would use it in XAML.
<ScrollViewer Name="IScroll">
<ListBox Name="IDont">
<i:Interaction.Behaviors>
<local:IgnoreMouseWheelBehavior />
</i:Interaction.Behaviors>
</ListBox>
</ScrollViewer>
Where the i namespace is:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
The answer you have referenced is exactly what is causing your problem, the ListBox (which is composed of among other things a ScrollViewer) inside your ScrollViewer catches the MouseWheel event and handles it, preventing it from bubbling and thus the ScrollViewer has no idea the event ever occurred.
Use the following extremely simple ControlTemplate for your ListBox to demonstrate (note it does not have a ScrollViewer in it and so the MouseWheel event will not be caught) The ScrollViewer will still scroll with the mouse over the ListBox.
<UserControl.Resources>
<ControlTemplate x:Key="NoScroll">
<ItemsPresenter></ItemsPresenter>
</ControlTemplate>
</UserControl.Resources>
<ScrollViewer>
<SomeContainerControl>
<.... what ever other controls are inside your ScrollViewer>
<ListBox Template="{StaticResource NoScroll}"></ListBox>
<SomeContainerControl>
</ScrollViewer>
You do have the option of capturing the mouse when it enters the ScrollViewer though so it continues to receive all mouse events until the mouse is released, however this option would require you to delgate any further mouse events to the controls contained within the ScrollViewer if you want a response...the following MouseEnter MouseLeave event handlers will be sufficient.
private void ScrollViewerMouseEnter(object sender, MouseEventArgs e)
{
((ScrollViewer)sender).CaptureMouse();
}
private void ScrollViewerMouseLeave(object sender, MouseEventArgs e)
{
((ScrollViewer)sender).ReleaseMouseCapture();
}
Neither of the workarounds I have provided are really preferred however and I would suggest rethinking what you are actually trying to do. If you explain what you are trying to achieve in your question I'm sure you will get some more suggestions...
I followed Amanduh's approach to solve the same problem I had with multiple datagrids in a scrollviewer but in WPF:
public sealed class IgnoreMouseWheelBehavior
{
public static bool GetIgnoreMouseWheel(DataGrid gridItem)
{
return (bool)gridItem.GetValue(IgnoreMouseWheelProperty);
}
public static void SetIgnoreMouseWheel(DataGrid gridItem, bool value)
{
gridItem.SetValue(IgnoreMouseWheelProperty, value);
}
public static readonly DependencyProperty IgnoreMouseWheelProperty =
DependencyProperty.RegisterAttached("IgnoreMouseWheel", typeof(bool),
typeof(IgnoreMouseWheelBehavior), new UIPropertyMetadata(false, OnIgnoreMouseWheelChanged));
static void OnIgnoreMouseWheelChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var item = depObj as DataGrid;
if (item == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
item.PreviewMouseWheel += OnPreviewMouseWheel;
else
item.PreviewMouseWheel -= OnPreviewMouseWheel;
}
static void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
var e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{RoutedEvent = UIElement.MouseWheelEvent};
var gv = sender as DataGrid;
if (gv != null) gv.RaiseEvent(e2);
}
}
As Simon said, it's the ScrollViewer in the standard ListBox template that's catching the event. To bypass it you can provide your own template.
<ControlTemplate x:Key="NoWheelScrollListBoxTemplate" TargetType="ListBox">
<Border BorderThickness="{TemplateBinding Border.BorderThickness}" Padding="1,1,1,1" BorderBrush="{TemplateBinding Border.BorderBrush}" Background="{TemplateBinding Panel.Background}" Name="Bd" SnapsToDevicePixels="True">
<!-- This is the new control -->
<l:NoWheelScrollViewer Padding="{TemplateBinding Control.Padding}" Focusable="False">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</l:NoWheelScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="UIElement.IsEnabled" Value="False">
<Setter TargetName="Bd" Property="Panel.Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
</Trigger>
<Trigger Property="ItemsControl.IsGrouping" Value="True">
<Setter Property="ScrollViewer.CanContentScroll" Value="False" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
And the implementation for NoWheelScrollViewer is pretty simple.
public class NoWheelScrollViewer : ScrollViewer
{
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
// Do nothing
}
}
Then, whenever you want a listbox to not handle the mouse wheel.
<ListBox Template="{StaticResource NoWheelScrollListBoxTemplate}">
I was trying to adapt Simon Fox's answer for a DataGrid. I found the the template hid my headers, and I never got the mouseLeave event by doing it in C#. This is ultimately what worked for me:
private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
((DataGrid)sender).CaptureMouse();
}
private void DataGrid_MouseWheel(object sender, MouseWheelEventArgs e)
{
((DataGrid)sender).ReleaseMouseCapture();
}
A simple solution which worked for me is to override the inner control template to remove the scroll viewer (whichever required) like this
For example
I have a structure like this
ListView (a)
ListView (b)
ListView (c)
I wanted to bubble the mouse wheel scroll of (b) to (a), however wanted to keep the mouse wheel scroll of (c) available.
I simply overridden the Template of (b) like this. This allowed me to bubble contents of (b) except (c) to (a). Also, I can still scroll the contents of (c). If i want to remove even for (c) then i have to repeat the same step.
<ListView.Template>
<ControlTemplate>
<ItemsPresenter />
</ControlTemplate>
</ListView.Template>
A modified Simon Fox's solution if the original doesn't work:
public sealed class IgnoreMouseWheelBehavior : Behavior<UIElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseWheel += AssociatedObject_PreviewMouseWheel;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewMouseWheel -= AssociatedObject_PreviewMouseWheel;
base.OnDetaching();
}
static void AssociatedObject_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (!(sender is DependencyObject))
{
return;
}
DependencyObject parent = VisualTreeHelper.GetParent((DependencyObject) sender);
if (!(parent is UIElement))
{
return;
}
((UIElement) parent).RaiseEvent(
new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) { RoutedEvent = UIElement.MouseWheelEvent });
e.Handled = true;
}
}
You must listening PreviewMouseWheel from ScrollViewer (it works), but not from listbox.
I have defined the following DataTemplate for ListBox items in an external resource dictionary:
<DataTemplate x:Key="MyListBoxItemTemplate" DataType="{x:Type entities:Track}">
<StackPanel>
<TextBlock Text="Here's the slider:" />
<Slider Name="MySlider" Height="23" Minimum="0" />
</StackPanel>
</DataTemplate>
I need to provide an event handler method for Slider's ValueChanged event. I don't know where am I supposed to write that code as it is impractical to specify event handler for a control within a template.
I've been googling for the solution and found that I should add the event handler in the override of the OnApplyTemplate() method. My guess is that it should look something like this or similar:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Is the following initialization even going to work!?!?
Slider MySlider = this.FindName("MySlider") as Slider;
SeekSlider.ValueChanged +=
new RoutedPropertyChangedEventHandler<double>(SeekSlider_ValueChanged);
}
But where should I write this method? Does OnApplyTemplate overriding only applies to ControlTemplates or is my scenario included as well? Should I provide ControlTemplate instead of DataTemplate? Is the body of the method I have provided correct?
Please help. Thanks.
Using the OnApplyTemplate approach will work if you if you're working with the ControlTemplate for a Control. For example, if you've subclassed TextBox you could do this like
public class MyTextBox : TextBox
{
public override void OnApplyTemplate()
{
MySlider MySlider = GetTemplateChild("MySlider") as MySlider;
if (MySlider != null)
{
MySlider.ValueChanged += new RoutedPropertyChangedEventHandler<double>(MySlider_ValueChanged);
}
base.OnApplyTemplate();
}
void MySlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
//...
}
}
I don't think this approach will work in your situation however. You could use the Loaded event for ListBoxItem and find the Slider in the visual tree in the event handler
<ListBox ...>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<EventSetter Event="Loaded" Handler="ListBoxItem_Loaded"/>
</Style>
</ListBox.ItemContainerStyle>
<!--...-->
</ListBox>
Code behind
private void ListBoxItem_Loaded(object sender, RoutedEventArgs e)
{
ListBoxItem listBoxItem = sender as ListBoxItem;
Slider MySlider = GetVisualChild<Slider>(listBoxItem);
MySlider.ValueChanged += new RoutedPropertyChangedEventHandler<double>(MySlider_ValueChanged);
}
void MySlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
}
GetVisualChild
private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
Little know fact is that ResourceDictionaries can hold CodeBehind as well..
As a general rule of thumb I don't think that putting DataTemplates in ResourceDictionaries is a good idea to begin with (your question being an example for one of the reasons), this is how you can solve it:
XAML:
<ResourceDictionary
x:Class="WpfApplication24.Dictionary1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate x:Key="MyDataTemplate">
<StackPanel>
<TextBlock Text="Hello" />
<Slider ValueChanged="ValueChanged"/>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
and code behind:
namespace WpfApplication24
{
public partial class Dictionary1 : ResourceDictionary
{
public void ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
Debug.Write("Hello");
}
}
}
Anyhow, as Meleak said above me - OnApplyTemplate is only relevant for Control Templates and not Data Templates.
Have a look at this question and answer.
I think that using Commands is the best option.
EDIT A good tutorial
You can use EventSetter in the style you are setting the template with:
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseWheel" Handler="GroupListBox_MouseWheel" />
<Setter Property="Template" ... />
</Style>
Method 1:
Use your own control inherited from Slider:
public class SpecialSlider : Slider
{
public SpecialSlider()
{
ValueChanged += OnValueChanged;
}
private void OnValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
// ...
}
}
Method 2: use behaviors from System.Windows.Interactivity.dll assembly (available through the NuGet):
public class SpecialSliderBehavior : Behavior<Slider>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.ValueChanged += OnValueChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.ValueChanged -= OnValueChanged;
}
private void OnValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
// ...
}
}
This is how to attach it:
...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
...
<DataTemplate x:Key="MyListBoxItemTemplate" DataType="{x:Type entities:Track}">
<Slider Name="MySlider">
<i:Interaction.Behaviors>
<SpecialSliderBehavior />
</i:Interaction.Behaviors>
</Slider>
</DataTemplate>
I have a usercontrol that has a scrollviewer, then a bunch of child controls like text boxes, radio buttons, and listboxes, etc inside of it. I can use the mouse wheel to scroll the parent scrollviewer until my mouse lands inside a listbox then, the mouse wheel events start going to the listbox.
Is there any way to have the listbox send those events back up to the parent control? Removing the listbox from within side the parent control like this question suggests (Mouse wheel not working when over ScrollViewer's child controls) isnt a solution.
I have tried
private void ListBox_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
}
but that didnt work either.
Thanks
This can be accomplished via attached behaviors.
http://josheinstein.com/blog/index.php/2010/08/wpf-nested-scrollviewer-listbox-scrolling/
Edit:
Here is the linked solution:
"So instead I came up with the following IgnoreMouseWheelBehavior. Technically it’s not ignoring the MouseWheel, but it is “forwarding” the event back up and out of the ListBox. Check it."
/// <summary>
/// Captures and eats MouseWheel events so that a nested ListBox does not
/// prevent an outer scrollable control from scrolling.
/// </summary>
public sealed class IgnoreMouseWheelBehavior : Behavior<UIElement>
{
protected override void OnAttached( )
{
base.OnAttached( );
AssociatedObject.PreviewMouseWheel += AssociatedObject_PreviewMouseWheel ;
}
protected override void OnDetaching( )
{
AssociatedObject.PreviewMouseWheel -= AssociatedObject_PreviewMouseWheel;
base.OnDetaching( );
}
void AssociatedObject_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
var e2 = new MouseWheelEventArgs(e.MouseDevice,e.Timestamp,e.Delta);
e2.RoutedEvent = UIElement.MouseWheelEvent;
AssociatedObject.RaiseEvent(e2);
}
}
And here’s how you would use it in XAML.
<ScrollViewer Name="IScroll">
<ListBox Name="IDont">
<i:Interaction.Behaviors>
<local:IgnoreMouseWheelBehavior />
</i:Interaction.Behaviors>
</ListBox>
</ScrollViewer>
Where the i namespace is:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
The answer you have referenced is exactly what is causing your problem, the ListBox (which is composed of among other things a ScrollViewer) inside your ScrollViewer catches the MouseWheel event and handles it, preventing it from bubbling and thus the ScrollViewer has no idea the event ever occurred.
Use the following extremely simple ControlTemplate for your ListBox to demonstrate (note it does not have a ScrollViewer in it and so the MouseWheel event will not be caught) The ScrollViewer will still scroll with the mouse over the ListBox.
<UserControl.Resources>
<ControlTemplate x:Key="NoScroll">
<ItemsPresenter></ItemsPresenter>
</ControlTemplate>
</UserControl.Resources>
<ScrollViewer>
<SomeContainerControl>
<.... what ever other controls are inside your ScrollViewer>
<ListBox Template="{StaticResource NoScroll}"></ListBox>
<SomeContainerControl>
</ScrollViewer>
You do have the option of capturing the mouse when it enters the ScrollViewer though so it continues to receive all mouse events until the mouse is released, however this option would require you to delgate any further mouse events to the controls contained within the ScrollViewer if you want a response...the following MouseEnter MouseLeave event handlers will be sufficient.
private void ScrollViewerMouseEnter(object sender, MouseEventArgs e)
{
((ScrollViewer)sender).CaptureMouse();
}
private void ScrollViewerMouseLeave(object sender, MouseEventArgs e)
{
((ScrollViewer)sender).ReleaseMouseCapture();
}
Neither of the workarounds I have provided are really preferred however and I would suggest rethinking what you are actually trying to do. If you explain what you are trying to achieve in your question I'm sure you will get some more suggestions...
I followed Amanduh's approach to solve the same problem I had with multiple datagrids in a scrollviewer but in WPF:
public sealed class IgnoreMouseWheelBehavior
{
public static bool GetIgnoreMouseWheel(DataGrid gridItem)
{
return (bool)gridItem.GetValue(IgnoreMouseWheelProperty);
}
public static void SetIgnoreMouseWheel(DataGrid gridItem, bool value)
{
gridItem.SetValue(IgnoreMouseWheelProperty, value);
}
public static readonly DependencyProperty IgnoreMouseWheelProperty =
DependencyProperty.RegisterAttached("IgnoreMouseWheel", typeof(bool),
typeof(IgnoreMouseWheelBehavior), new UIPropertyMetadata(false, OnIgnoreMouseWheelChanged));
static void OnIgnoreMouseWheelChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var item = depObj as DataGrid;
if (item == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
item.PreviewMouseWheel += OnPreviewMouseWheel;
else
item.PreviewMouseWheel -= OnPreviewMouseWheel;
}
static void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
var e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{RoutedEvent = UIElement.MouseWheelEvent};
var gv = sender as DataGrid;
if (gv != null) gv.RaiseEvent(e2);
}
}
As Simon said, it's the ScrollViewer in the standard ListBox template that's catching the event. To bypass it you can provide your own template.
<ControlTemplate x:Key="NoWheelScrollListBoxTemplate" TargetType="ListBox">
<Border BorderThickness="{TemplateBinding Border.BorderThickness}" Padding="1,1,1,1" BorderBrush="{TemplateBinding Border.BorderBrush}" Background="{TemplateBinding Panel.Background}" Name="Bd" SnapsToDevicePixels="True">
<!-- This is the new control -->
<l:NoWheelScrollViewer Padding="{TemplateBinding Control.Padding}" Focusable="False">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</l:NoWheelScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="UIElement.IsEnabled" Value="False">
<Setter TargetName="Bd" Property="Panel.Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
</Trigger>
<Trigger Property="ItemsControl.IsGrouping" Value="True">
<Setter Property="ScrollViewer.CanContentScroll" Value="False" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
And the implementation for NoWheelScrollViewer is pretty simple.
public class NoWheelScrollViewer : ScrollViewer
{
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
// Do nothing
}
}
Then, whenever you want a listbox to not handle the mouse wheel.
<ListBox Template="{StaticResource NoWheelScrollListBoxTemplate}">
I was trying to adapt Simon Fox's answer for a DataGrid. I found the the template hid my headers, and I never got the mouseLeave event by doing it in C#. This is ultimately what worked for me:
private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
((DataGrid)sender).CaptureMouse();
}
private void DataGrid_MouseWheel(object sender, MouseWheelEventArgs e)
{
((DataGrid)sender).ReleaseMouseCapture();
}
A simple solution which worked for me is to override the inner control template to remove the scroll viewer (whichever required) like this
For example
I have a structure like this
ListView (a)
ListView (b)
ListView (c)
I wanted to bubble the mouse wheel scroll of (b) to (a), however wanted to keep the mouse wheel scroll of (c) available.
I simply overridden the Template of (b) like this. This allowed me to bubble contents of (b) except (c) to (a). Also, I can still scroll the contents of (c). If i want to remove even for (c) then i have to repeat the same step.
<ListView.Template>
<ControlTemplate>
<ItemsPresenter />
</ControlTemplate>
</ListView.Template>
A modified Simon Fox's solution if the original doesn't work:
public sealed class IgnoreMouseWheelBehavior : Behavior<UIElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseWheel += AssociatedObject_PreviewMouseWheel;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewMouseWheel -= AssociatedObject_PreviewMouseWheel;
base.OnDetaching();
}
static void AssociatedObject_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (!(sender is DependencyObject))
{
return;
}
DependencyObject parent = VisualTreeHelper.GetParent((DependencyObject) sender);
if (!(parent is UIElement))
{
return;
}
((UIElement) parent).RaiseEvent(
new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) { RoutedEvent = UIElement.MouseWheelEvent });
e.Handled = true;
}
}
You must listening PreviewMouseWheel from ScrollViewer (it works), but not from listbox.
I have the same command that I want to use for two controls on a dialog type window. As potentially interesting background, I'm using Josh Smith's ViewModel / RelayCommand ideas, since I am new to WPF and it's the first thing I've seen that I can actually understand from a big picture point of view.
So the command is a property of a ViewModel, and with the Button's built-in support, it is trivial and painless to bind to the command in the XAML:
<Button ... Command="{Binding Path=PickCommand}" Content="_Ok"></Button>
Now in a ListView, the only way I have gotten to use the same command hooked up to trigger on a double click is by using an event handler:
<ListView ...
ItemsSource="{Binding Path=AvailableProjects}"
SelectedItem="{Binding Path=SelectedProject, Mode=TwoWay}"
MouseDoubleClick="OnProjectListingMouseDoubleClick"
>
private void OnProjectListingMouseDoubleClick(object sender, MouseButtonEventArgs e) {
var vm = (ProjectSelectionViewModel) DataContext;
vm.Pick(); // execute the pick command
}
Is there a way to do this by binding the way the button does it?
Cheers,
Berryl
<------- implementation - is there a better way? --->
Your SelctionBehavior class was spot on, but I was confused at your xaml code. By setting the "Style" on the listViewItem I was getting the children of the DataContext where the command I want to execute lives. So I attached the behavior to the ListView itself:
<ListView ...Style="{StaticResource _attachedPickCommand}" >
And put the style in a resource dictionary:
<Style x:Key="_attachedPickCommand" TargetType="ListView">
<Setter Property="behaviors:SelectionBehavior.DoubleClickCommand" Value="{Binding Path=PickCommand}" />
</Style>
It works! But it 'feels' awkward setting the style property of the list view. Is this just because I am not comfortable with style as more than something visual in wpf or is there a better way to do this?
Cheers, and thanks!
Berryl
Yes there is! You can use attached behaviors and bind the command to that behavior.
public class SelectionBehavior {
public static readonly DependencyProperty CommandParameterProperty=
DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(SelectionBehavior));
public static readonly DependencyProperty DoubleClickCommandProperty=
DependencyProperty.RegisterAttached("DoubleClickCommand", typeof(ICommand), typeof(SelectionBehavior),
new PropertyMetadata(OnDoubleClickAttached));
private static void OnDoubleClickAttached(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var fe=(FrameworkElement)d;
if(e.NewValue!=null && e.OldValue==null) {
fe.PreviewMouseDown+=fe_MouseDown;
} else if(e.NewValue==null && e.OldValue!=null) {
fe.PreviewMouseDown-=fe_MouseDown;
}
}
private static void fe_MouseDown(object sender, MouseButtonEventArgs e) {
if(e.ClickCount==2) {
var dep=(FrameworkElement)sender;
var command=GetDoubleClickCommand(dep);
if(command!=null) {
var param=GetCommandParameter(dep);
command.Execute(param);
}
}
}
public static ICommand GetDoubleClickCommand(FrameworkElement element) {
return (ICommand)element.GetValue(DoubleClickCommandProperty);
}
public static void SetDoubleClickCommand(FrameworkElement element, ICommand value) {
element.SetValue(DoubleClickCommandProperty, value);
}
public static object GetCommandParameter(DependencyObject element) {
return element.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(DependencyObject element, object value) {
element.SetValue(CommandParameterProperty, value);
}
}
and in the xaml you would need to set a style for a ListViewItem which represents your data in the ListView. Example
<ListView>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="local:SelectionBehavior.DoubleClickCommand" Value="{Binding Path=DataContext.PickCommand}"/>
<Setter Property="local:SelectionBehavior.CommandParameter" Value="{Binding Path=DataContext}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Here is some more information about the Attached Behavior pattern
I have a data template with a textbox and a button with some styles on it. I would like to have the button show the mouse over state when focus is on the textbox beside it. Is this possible?
I figure it would involve something like this. I can get the textbox through use of FindVisualChild and FindName. Then I can set the GotFocus event on the textbox to do something.
_myTextBox.GotFocus += new RoutedEventHandler(TB_GotFocus);
Here in TB_GotFocus I'm stuck. I can get the button I want to show the mouse over state of, but I don't know what event to send to it. MouseEnterEvent isn't allowed.
void TB_GotFocus(object sender, RoutedEventArgs e)
{
ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(this.DataTemplateInstance);
DataTemplate template = myContentPresenter.ContentTemplate;
Button _button= template.FindName("TemplateButton", myContentPresenter) as Button;
_button.RaiseEvent(new RoutedEventArgs(Button.MouseEnterEvent));
}
I don't think it's possible to fake the event but you can force the button to render itself as if it had MouseOver.
private void tb_GotFocus(object sender, RoutedEventArgs e)
{
// ButtonChrome is the first child of button
DependencyObject chrome = VisualTreeHelper.GetChild(button, 0);
chrome.SetValue(Microsoft.Windows.Themes.ButtonChrome.RenderMouseOverProperty, true);
}
private void tb_LostFocus(object sender, RoutedEventArgs e)
{
// ButtonChrome is the first child of button
DependencyObject chrome = VisualTreeHelper.GetChild(button, 0);
chrome.ClearValue(Microsoft.Windows.Themes.ButtonChrome.RenderMouseOverProperty);
}
you need to reference PresentationFramework.Aero.dlll for this to work and then it will only work on Vista for the Aero theme.
If you want it to work for other themes you should make a custom controltemplate for each of the theme you want to support.
See http://blogs.msdn.com/llobo/archive/2006/07/12/663653.aspx for tips
As a follow up to jesperll's comment, I think you can get around making a custom template for each theme by dynamically setting the style to the one you want / null.
Here is my window, with the style defined (but not set to anything).
<Window x:Class="WpfApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style TargetType="{x:Type Button}" x:Key="MouseOverStyle">
<Setter Property="Background">
<Setter.Value>Green</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Height="30">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="MyTextBox" Grid.Column="0" Text="Some Text" Margin="2" GotFocus="TextBox_GotFocus" LostFocus="MyTextBox_LostFocus"/>
<Button x:Name="MyButton" Grid.Column="1" Content="Button" Margin="2" MouseEnter="Button_MouseEnter" MouseLeave="Button_MouseLeave" />
</Grid>
Instead of setting the style via triggers in the template, you can use events in your .cs file like so:
...
public partial class Window1 : Window
{
Style mouseOverStyle;
public Window1()
{
InitializeComponent();
mouseOverStyle = (Style)FindResource("MouseOverStyle");
}
private void TextBox_GotFocus(object sender, RoutedEventArgs e) { MyButton.Style = mouseOverStyle; }
private void MyTextBox_LostFocus(object sender, RoutedEventArgs e) { MyButton.Style = null; }
private void Button_MouseEnter(object sender, MouseEventArgs e) { ((Button)sender).Style = mouseOverStyle; }
private void Button_MouseLeave(object sender, MouseEventArgs e) { ((Button)sender).Style = null; }
}
You get a reference to the style in the constructor and then dynamically set it / unset it. This way, you can define what you want your style to look like in Xaml, and you don't have to rely on any new dependencies.