How to have interaction behavior work on a UserControl - c#

I have the following UserControl that has an attached behavior on it. At the moment I have to select one of the DataGrid items in order for it to run the behavior.
Only guessing here but it's kind of understandable because these containers are not focusable or are not supposed to register keyboard events, so it goes to the next child available???
I could place this functionality on the window however this UserControl can get swapped out with another UserControl and I prefer for it not be be lying around.
This is just a unique case in my app where the only thing on the window is the DataGrid and I don't necessarily want to select a DataGrid item to begin the behavior.
My question is: Is it possible to get the following behavior to work? Either on the UserControl, Grid or maybe some other control that would permit this.
<UserControl x:Class="UserManagement.LaunchPad"
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:helpers="clr-namespace:UserManagement.Helpers"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<i:Interaction.Behaviors>
<helpers:OpenPopupBehaviors FindPopupObject="{Binding ElementName=PopupFind}"
GotoPopupObject="{Binding ElementName=PopupGoto}" />
</i:Interaction.Behaviors>
<Grid>
<DockPanel>
<DataGrid />
</DockPanel>
<Popup Name="PopupGoto" />
<Popup Name="PopupFilter "/>
</Grid>
</UserControl>
Here is the behavior:
class OpenPopupBehaviors : Behavior<UserControl>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.KeyDown += _KeyBoardBehaviorKeyDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.KeyDown -= _KeyBoardBehaviorKeyDown;
}
void _KeyBoardBehaviorKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.F && (Keyboard.Modifiers & (ModifierKeys.Control)) == (ModifierKeys.Control))
{
var popup = FindPopupObject as UserManagement.Controls.NonTopmostPopup;
if (popup == null)
return;
popup.IsOpen = true;
}
if (e.Key == Key.G && (Keyboard.Modifiers & (ModifierKeys.Control)) == (ModifierKeys.Control))
{
var popup = GotoPopupObject as Popup;
if (popup == null)
return;
popup.IsOpen = true;
}
}
public static readonly DependencyProperty FindPopupObjectProperty =
DependencyProperty.RegisterAttached("FindPopupObject", typeof(object), typeof(OpenPopupBehaviors), new UIPropertyMetadata(null));
public object FindPopupObject
{
get { return (object)GetValue(FindPopupObjectProperty); }
set { SetValue(FindPopupObjectProperty, value); }
}
public static readonly DependencyProperty GotoPopupObjectProperty =
DependencyProperty.RegisterAttached("GotoPopupObject", typeof(object), typeof(OpenPopupBehaviors), new UIPropertyMetadata(null));
public object GotoPopupObject
{
get { return (object)GetValue(GotoPopupObjectProperty); }
set { SetValue(GotoPopupObjectProperty, value); }
}
}

So far I can see is that the focus if your issue
you can make use of Preview events to receive the events even if the focus is on control's child or their descendants
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.PreviewKeyDown += _KeyBoardBehaviorKeyDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.PreviewKeyDown-= _KeyBoardBehaviorKeyDown;
}
I have changed KeyDown to PreviewKeyDown
above solution will work as long as the AssociatedObject have focus on it or any child within it. if you look forward to control it globally then perhaps you may need to attach the events to top level elements eg. Window
so in order to do that you need to find the appropriate parent of AssociatedObject and attach the event to the same.
also make sure that the following cast is valid
FindPopupObject as UserManagement.Controls.NonTopmostPopup;
it could be
FindPopupObject as Popup;

Related

Popup doesn't open if StaysOpen="False"

If I change StaysOpen to "True", the popup shows up, but it doesn't close when you click outside of it, so that's not what I want.
Here is the relevant XAML code:
<Border x:Name="popupPlacementTarget">
<i:Interaction.Behaviors>
<local:PopupBehavior>
<local:PopupBehavior.Popup>
<Popup PlacementTarget="{Binding ElementName=popupPlacementTarget}"
Placement="MousePoint"
StaysOpen="False">
<ContentPresenter Content="{Binding SomeContent}" ContentTemplate="{StaticResource SomeContentTemplate}" />
</Popup>
<local:PopupBehavior.Popup>
</local:PopupBehavior>
</i:Interaction.Behaviors>
</Border>
And here is the PopupBehavior code:
public class PopupBehavior : Behavior<UIElement>
{
#region Dependency Properties
public static readonly DependencyProperty PopupProperty = DependencyProperty.Register(
"Popup", typeof(Popup), typeof(PopupBehavior), new FrameworkPropertyMetadata(default(Popup)));
#endregion Dependency Properties
#region Properties
public Popup Popup
{
get { return (Popup)this.GetValue(PopupProperty); }
set { this.SetValue(PopupProperty, value); }
}
#endregion Properties
#region Protected Methods
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.MouseDown += this.OnMouseDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.MouseDown -= this.OnMouseDown;
}
#endregion Protected Methods
#region Private Methods
private void OnMouseDown(object sender, MouseButtonEventArgs eventArgs)
{
var popup = this.Popup;
if (popup == null)
{
return;
}
this.Popup.IsOpen = true;
}
#endregion Private Methods
}
Any idea why my popup won't show up with StaysOpen="False"?
It turns out there was an ancestor that was indiscriminately grabbing capture on mouse down. Everything is working correctly now that I've corrected this issue.
As an aside, I was able to hack around this problem by setting StaysOpen="True", inheriting Popup, and hooking in to the global mouse events within the derived Popup. When the popup opens, I'd attach the handler to the global input events. Then, when an event is received, I'd filter it such that I'm only responding to left mouse button down events. In handling this event, I'd close the popup and detach the event if the mouse is not hovering the popup. It worked, but it is obviously a dirty hack, and I'm happy that I got this working without it.

Set focus back to its parent?

From post WPF: How to programmatically remove focus from a TextBox, I know how to set a TextBox's focus back to its parent using the following code:
// Move to a parent that can take focus
FrameworkElement parent = (FrameworkElement)textBox.Parent;
while (parent != null && parent is IInputElement
&& !((IInputElement)parent).Focusable)
{
parent = (FrameworkElement)parent.Parent;
}
DependencyObject scope = FocusManager.GetFocusScope(textBox);
FocusManager.SetFocusedElement(scope, parent as IInputElement);
Is there any way to generalize this code (like template functions) to make it also work for other items like ComboBox, Canvas, Image etc.
It should be relatively straightforward:
FrameworkElement ctrl = control; //or whatever you're passing in, since all controls are FrameworkElements.
// Move to a parent that can take focus
FrameworkElement parent = (FrameworkElement)ctrl.Parent;
while (parent != null && parent is IInputElement
&& !((IInputElement)parent).Focusable)
{
parent = (FrameworkElement)parent.Parent;
}
DependencyObject scope = FocusManager.GetFocusScope(ctrl); //can pass in ctrl here because FrameworkElement inherits from DependencyObject
FocusManager.SetFocusedElement(scope, parent as IInputElement);
This works because all controls inherit from FrameworkElement which inherits from DependencyObject. So you can set ctrl to any type of control you want: ComboBox, TextBox, Button, Canvas, etc.
Yes, there is such a possibility, in such cases, implement attached behavior. Logic at work with a focus must fit into DependencyPropertyChangedEvent handler.
Here is an example for your case:
XAML
<Window x:Class="RemoveFocusHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:RemoveFocusHelp"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Name="TestTextBox"
this:RemoveFocusBehavior.IsRemoveFocus="False"
Width="100"
Height="25"
Text="TestText" />
<Button Name="CreateFocus"
Width="100"
Height="30"
Content="Create Focus"
HorizontalAlignment="Left"
Click="CreateFocus_Click" />
<Button Name="RemoveFocus"
Focusable="False"
Width="100"
Height="30"
Content="Remove Focus"
HorizontalAlignment="Right"
Click="RemoveFocus_Click" />
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void CreateFocus_Click(object sender, RoutedEventArgs e)
{
TestTextBox.Focus();
}
private void RemoveFocus_Click(object sender, RoutedEventArgs e)
{
RemoveFocusBehavior.SetIsRemoveFocus(TestTextBox, true);
}
}
public class RemoveFocusBehavior
{
#region IsRemoveFocus Dependency Property
public static readonly DependencyProperty IsRemoveFocusProperty;
public static void SetIsRemoveFocus(DependencyObject DepObject, bool value)
{
DepObject.SetValue(IsRemoveFocusProperty, value);
}
public static bool GetIsRemoveFocus(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(IsRemoveFocusProperty);
}
static RemoveFocusBehavior()
{
IsRemoveFocusProperty = DependencyProperty.RegisterAttached("IsRemoveFocus",
typeof(bool),
typeof(RemoveFocusBehavior),
new UIPropertyMetadata(false, IsRemoveFocusTurn));
}
#endregion
#region IsRemoveFocus Property Metadata
private static void IsRemoveFocusTurn(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Control control = sender as Control;
if (control == null)
{
return;
}
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
FrameworkElement parent = (FrameworkElement)control.Parent;
while (parent != null && parent is IInputElement
&& !((IInputElement)parent).Focusable)
{
parent = (FrameworkElement)parent.Parent;
}
DependencyObject scope = FocusManager.GetFocusScope(control);
FocusManager.SetFocusedElement(scope, parent as IInputElement);
}
}
#endregion
}
Project is available at this link.

MouseButtonEventArgs.MouseDevice.DirectlyOver misunderstanding

I was faced with the next misunderstanding.
Preamble:
I have wpf application with next essential UI parts: RadioButtons and some control that use dropdown based on Popup (in combobox manner). According to some logic every radiobutton hook PreviewMouseDown event and do some calculations.
In the next scenario,
User opens popup (do not select something, popup just staying open)
User click on radiobutton
PreviewMouseDown will not be fired for radiobutton as expected (because of Popup feature).
And my aim is firing PreviewMouseDown for RadioButton despite of one.
Attempts to solve:
Fast and dirty solution is: hook PreviewMouseDown for Popup and re-fire PreviewMouseDown event with new source if required, using radiobutton as source. New source can be obtained via MouseButtonEventArgs.MouseDevice.DirectlyOver. The next piece of code do that (event is re-fired only if Popup "eat" PreviewMouseDown for outer click):
private static void GrantedPopupPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var popup = sender as Popup;
if(popup == null)
return;
var realSource = e.MouseDevice.DirectlyOver as FrameworkElement;
if(realSource == null || !realSource.IsLoaded)
return;
var parent = LayoutTreeHelper.GetParent<Popup>(realSource);
if(parent == null || !Equals(parent, popup ))
{
e.Handled = true;
var args = new MouseButtonEventArgs(e.MouseDevice,
e.Timestamp,
e.ChangedButton)
{
RoutedEvent = UIElement.PreviewMouseDownEvent,
Source = e.MouseDevice.DirectlyOver,
};
realSource.RaiseEvent(args);
}
}
This works well when I'm attaching that handler to Popup.PreviewMouseDown directly via Behavior and do not work (PreviewMouseDown isn't fired for radiobutton) if I'm attaching one via EventManager.RegisterClassHandler (aim is to avoid attaching behavior to every Popup that can occure on page with these radiobuttons):
EventManager.RegisterClassHandler(
typeof (Popup),
PreviewMouseDownEvent,
new MouseButtonEventHandler(GrantedPopupPreviewMouseDown));
Debugger showed that e.MouseDevice.DirectlyOver (see code above) is Popup, not Radiobutton (as it is was when I've attached handler via Behavior)!
Question:
How and whyMouseButtonEventArgs can be different for the same action, if eventhandler attached in two different ways?
Can someone explaing this behavior?
Thanks a lot.
The combo box is provided as a way for users to select from a group of options, and you likely want to do that. But it also has other contracts. It says that the user should be focused on this and only this task. But that is not your situation. You want to show the options, have them hide able, and allow the user to do other things while they are shown.
I think instead of combo boxes you want some other control. My suggestion is to use an expander that contains a listbox. Given:
class NotificationObject : INotifyPropertyChanged
{
public void RaisePropertyChanged(string name)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
class ComboEntry : NotificationObject
{
public string Name { get; private set; }
private string _option = "Off";
public string Option
{
get { return _option; }
set { _option = value; RaisePropertyChanged("Option"); }
}
public ComboEntry()
{
Name = Guid.NewGuid().ToString();
}
}
class MyDataContext : NotificationObject
{
public ObservableCollection<ComboEntry> Entries { get; private set; }
private ComboEntry _selectedEntry;
public ComboEntry SelectedEntry
{
get { return _selectedEntry; }
set { _selectedEntry = value; RaisePropertyChanged("SelectedEntry"); }
}
public MyDataContext()
{
Entries = new ObservableCollection<ComboEntry>
{
new ComboEntry(),
new ComboEntry(),
new ComboEntry()
};
SelectedEntry = Entries.FirstOrDefault();
}
public void SetOption(string value)
{
Entries
.ToList()
.ForEach(entry => entry.Option = value);
}
}
I think you want the following XAML:
<Window x:Class="RadioInCombo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RadioInCombo"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:MyDataContext x:Key="myDataContext" />
<DataTemplate x:Key="ComboEntryTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<Border Width="5" />
<TextBlock Text="{Binding Option}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel DataContext="{StaticResource myDataContext}">
<RadioButton x:Name="OnButton"
Content="On"
PreviewMouseDown="OnButton_PreviewMouseDown" />
<RadioButton x:Name="OffButton"
Content="Off"
PreviewMouseDown="OffButton_PreviewMouseDown" />
<Expander Header="{Binding SelectedEntry}"
HeaderTemplate="{StaticResource ComboEntryTemplate}">
<ListBox ItemsSource="{Binding Entries}"
ItemTemplate="{StaticResource ComboEntryTemplate}" />
</Expander>
</StackPanel>
</Window>
And the following code-behind:
private MyDataContext GetMyDataContext()
{
var candidate = FindResource("myDataContext") as MyDataContext;
if (candidate == null) throw new ApplicationException("Could not locate the myDataContext object");
return candidate;
}
private void OnButton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
GetMyDataContext().SetOption("On");
}
private void OffButton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
GetMyDataContext().SetOption("Off");
}

When does binding actually happen

On a popup window I have a checkbox.IsChecked bound to a model, but I want to check its state from the xaml code behind when the window is displayed. When checking the checkbox by name in the code behind from the windows loaded event it is not set yet. There are some UI specific things that I need to do and that is why I need to know what the checkboxes value is when the window opens and i cannot perform this from the model that the checkbox.IsChecked is bound to.
The property on the model is set long before the popup window is opened, so it is not an issue of the binding not being there. I figured that once the Loaded event fires the window would be ready to use bindings and all, but this does not seem to be the case.
Xaml:
<RefinedRibbonControls:RefinedRibbonGroup Header="Show Graphs">
<StackPanel x:Name="panelVisibilities">
<CheckBox Content="Show/hide" x:Name="myCheckBox"
IsChecked="{Binding Path=Processor.Model.IsItemVisible}"
Click="GraphVisibilityClickEvent"
HorizontalAlignment="Left"/>
...etc
Property on model:
public bool IsItemVisible
{
get { return _isItemVisible ; }
set
{
if (_isItemVisible != value)
{
_isItemVisible = value;
_propertyChanger.FirePropertyChanged(this, m => m.IsItemVisible);
}
}
}
Event in Xaml codebehind:
private void WindowLoadedEvent(object sender, RoutedEventArgs e)
{
if(myCheckBox.IsChecked.Value)
{
// Do UI Related Stuff
}
}
The binding works fine and the values show up when the window is displayed, the problem is I cannot get the value of the binding in the window loaded event.
Edit: Possible solution I have found but I am not sure if its the best way.
I called the following method from the constructor on the xaml code behind.
private void SetupInitialVisibility()
{
//Fire after everything is loaded.
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>
{
IEnumerable<CheckBox> elements = this.panelVisibilities.Children.OfType<CheckBox>().ToList();
foreach (CheckBox checkBox in elements)
{
if (checkBox.IsChecked != null && checkBox.IsChecked.Value == false)
{
//Do work
}
}
}));
}
found at: https://stackoverflow.com/a/1746975/1253746
Data binding is not done synchronously, but it is delayed. Check this msdn page on dispatcher priorities. It is done at a lower priority than normal window messages, but before rendering.
You could invoke a method on yourself with a lower priority than is defined for databinding, in this method you should be able to safely read the data bound value.
I would still find this ugly. I'd rather subscribe directly to PropertyChanged and check for this property, or even better, rewrite your "UI related code" as a data binding.
P.S. If you start consuming events, be sure to unsubscribe, or you might get memory leaks.
DataBinding should precede the Loaded event I think.
When and how do you set your DataContext? And you are positive that the viewmodel property is already set?
The following works, try to align your code with this if possible.
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<CheckBox x:Name="myCheckBox" IsChecked="{Binding IsItemVisible}" />
</Grid>
</Window>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
if (myCheckBox.IsChecked.Value)
{
//...
}
}
}
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isItemVisible;
public bool IsItemVisible { get { return isItemVisible; } set { isItemVisible = value; OnPropertyChanged("IsItemVisible"); } }
public ViewModel()
{
this.IsItemVisible = true;
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

TextBox LostFocus does not fire

I have a problem with LostFocus event it does not fire when I click on the background.I read some stuff about focus logic and keyboard focus but I could not find a way to get the focus from a control a like textbox when there is only one of them
XAML:
<Grid Height="500" Width="500">
<TextBox Height="23" Width="120" Margin="12,12,0,0" Name="textBox1" LostFocus="textBox1_LostFocus" />
</Grid>
C#:
private void textBox1_LostFocus(object sender, RoutedEventArgs e)
{
}
You must use the following tunnelling event : PreviewLostKeyboardFocus on your textbox
Tunneling: Initially, event handlers at the element tree root are
invoked. The routed event then travels a route through successive
child elements along the route, towards the node element that is the
routed event source (the element that raised the routed event).
Tunneling routed events are often used or handled as part of the
compositing for a control, such that events from composite parts can
be deliberately suppressed or replaced by events that are specific to
the complete control. Input events provided in WPF often come
implemented as a tunneling/bubbling pair. Tunneling events are also
sometimes referred to as Preview events, because of a naming
convention that is used for the pairs.
The following behavior will fix this:
public class TextBoxUpdateOnLostKeyboardFocusBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
if (AssociatedObject != null)
{
base.OnAttached();
AssociatedObject.LostKeyboardFocus += OnKeyboardLostFocus;
}
}
protected override void OnDetaching()
{
if (AssociatedObject != null)
{
AssociatedObject.LostKeyboardFocus -= OnKeyboardLostFocus;
base.OnDetaching();
}
}
private void OnKeyboardLostFocus(object sender, KeyboardFocusChangedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox != null && e.NewFocus == null)
{
// Focus on the closest focusable ancestor
FrameworkElement parent = (FrameworkElement) textBox.Parent;
while (parent is IInputElement && !((IInputElement) parent).Focusable)
{
parent = (FrameworkElement) parent.Parent;
}
DependencyObject scope = FocusManager.GetFocusScope(textBox);
FocusManager.SetFocusedElement(scope, parent);
}
}
}
You can attach it to your TextBox as follows:
<TextBox>
<i:Interaction.Behaviors>
<behaviors1:TextBoxUpdateOnLostKeyboardFocusBehavior />
</i:Interaction.Behaviors>
</TextBox>

Categories

Resources