Focus abstraction - c#

UserControl with buttons (some of them are disabled) is nested inside other UserControl. There are several of such displayed in the window at once.
Now I need to set focus to first enabled button of nested UserControl, while the logic to choose focus will run on the level of window (e.g. when window will enable certain UserControl).
I need to be able to pass that focus request (via properties?) through several ViewModels and finally trigger it in the View of nested UserControl.
Ho can I abstract focus request? E.g. I want to be able to tell "set focus to this high level UserControl" and that should somehow automatically go through nested UserControl and its buttons, because only button is the element what can receive focus.
Pseudo-code:
// in window
UserControlA.Focus();
// should in fact set focus to 4th button of nested user control
UserControlA.UserControlB.ButtonD.Focus();
// because of data templates it is actually more like this
var nested = UserControlA.ContentControl.Content as UserControlB;
var firstEnabledButton = nested.ItemsControl[3] as Button;
firstEnabledButton.SetFocus();
// and because of MVVM it may be as simple as
ViewModelA.IsFocused = true;
// but then A should run
ViewModelB.IsFocused = true;
// and then B should set property of button ViewModel
Buttons.First(o => o.IsEnabled).IsFocused = true.
// and then this has to be somehow used by the view (UserControlB) to set focus...
Problem is not with how to set focus in MVVM, this can be done somehow (with triggers it needs ugly workaround where property is first set to false). My problem is how to pass that request ("and then ..., and then ..., and then..." in example above).
Any ideas?
I am looking for a simple and intuitive xaml solution with the most reusability. I don't want to spam every ViewModel and views with ...IsFocused properties and bindings.
I can use some side effect to my advantage, e.g. consider this behavior
public static bool GetFocusWhenEnabled(DependencyObject obj) => (bool)obj.GetValue(FocusWhenEnabledProperty);
public static void SetFocusWhenEnabled(DependencyObject obj, bool value) => obj.SetValue(FocusWhenEnabledProperty, value);
public static readonly DependencyProperty FocusWhenEnabledProperty =
DependencyProperty.RegisterAttached("FocusWhenEnabled", typeof(bool), typeof(FocusBehavior), new PropertyMetadata(false, (d, e) =>
{
var element = d as UIElement;
if (element == null)
throw new ArgumentException("Only used with UIElement");
if ((bool)e.NewValue)
element.IsEnabledChanged += FocusWhenEnabled_IsEnabledChanged;
else
element.IsEnabledChanged -= FocusWhenEnabled_IsEnabledChanged;
}));
static void FocusWhenEnabled_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var element = (UIElement)sender;
if (element.IsEnabled)
element.Dispatcher.InvokeAsync(() => element.Focus()); // invoke is a must
}
which can be used to automatically focus enabled element. This require some IsEnabled logic in addition and will easily stop working in some complicated scenarios (where enabling should not cause the focusing).
I am thinking if I can add some attached property to pass focus requests all the way through xaml (using only xaml) when attempting to set focus to container, which is not focusable.

I think you should consider using the FrameworkElement.MoveFocus method together with FocusNavigationDirection.Next - this should in general give you the expected result, i.e. give focus to the first encountered control which can receive keyboard focus. In particular that means that non-focusable controls, disabled controls, and controls that cannot receive keyboard focus (such as ItemsControl, UserControl etc.) will be omitted. The only catch here is that the controls will be traversed in tab order, but unless you're messing around with that it should traverse the visual tree in depth-first pre-order manner. So this code:
UserControlA.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
should give focus to UserControlA.UserControlB.ButtonD if it is the first keyboard-focusable and enabled descendant of UserControlA.
In terms of dismissing the necessity to use code-behind what I'd do is the following. First of all I'd drop using view-model properties to control focus. Moving focus seems to me a lot more like request-based concept rather than state-based, so I'd use events (e.g. FocusRequested) instead. To make it reusable I'd create a one-event interface (e.g. IRequestFocus). The final touch would be to create a behavior that would automatically inspect if DataContext of the attached object implements IRequestFocus and call MoveFocus each time the FocusRequested event is raised.
With such setup all you'd need to do is to implement IRequestFocus in ViewModelA, and attach the behavior to UserControlA. Then simply raising the FocusRequested in ViewModelA would result in moving focus to UserControlA.UserControlB.ButtonD.

Related

Prism.WPF: Change MainWindow Window.Effect on Navigation

The application is a Prism Application in WPF using C#.
I am attempting to assign a BlurEffect to the Window.Effect property when a button is clicked on the navigation menu.
I have the Window.Effect bound to a property in my viewmodel.
<Window ... other properties ..
Effect = {Binding Fuzzy}>
and the Fuzzy property in the ViewModel.
private Effect _fuzzy;
public Effect Fuzzy { get => _fuzzy; set => SetProperty(ref _fuzzy, value); }
What I am attempting to implement is that when a button is clicked on the navigation menu that the window will blur while a UserControl is loading.
I have tried to implement the change in the Navigate method.
private void Navigate(string viewName)
{
PerformBlur();
_regionManager.RequestNavigate("ContentRegion", viewName);
}
private void PerformBlur()
{
BlurEffect blur = new BlurEffect();
blur.Radius = 4;
var ef = blur;
_fuzzy = ef; //I've tried Fuzzy = ef too
}
But that doesn't work.
I need to make the change to the window effect before it attempts to navigate, and I haven't been able to figure out how to make that happen. I have a feeling that the easiest way to do this would be to use a click event rather than a command, and then call the command in the viewmodel from the codebehind. However, that doesn't seem to be the proper implementation when using MVVM. Any suggestions on how to implement this functionality would be greatly appreciated.
(Bonus points if you can tell me how to animate the blur. lol)
I have a feeling that the easiest way to do this would be to use a click event rather than a command, and then call the command in the viewmodel from the codebehind. However, that doesn't seem to be the proper implementation when using MVVM.
Invoking the command programmatically from the code-behind of the view is not any worse than invoking it from the XAML markup of the very same view as far as MVVM is concerned.
MVVM is not about eliminating code from the views. It's about separation of concerns. You can implement an entire view programmtically in a C# without using XAML at all and still be fully compliant with MVVM.
Trying to do fairly complex stuff in XAML just because you possible can is generally considered as an antipattern. Remember that XAML is a markup language. C# is a much more expressive and concise language so if you can solve your issue by writing some code, then this is most probably what you should do.
Taking a look at the prism source code, I can see iregionmanager is full of abstracted interfaces.
( Wow. I don't know why it still surprises me but prism is very complicated ).
https://github.com/PrismLibrary/Prism/blob/master/src/Wpf/Prism.Wpf/Regions/IRegion.cs
That includes IRegionNavigationService
https://github.com/PrismLibrary/Prism/blob/master/src/Wpf/Prism.Wpf/Regions/IRegionNavigationService.cs
You could therefore override pretty much any functionality you like, if you wanted to.
Notice though, the two events :
/// <summary>
/// Raised when the region is about to be navigated to content.
/// </summary>
event EventHandler<RegionNavigationEventArgs> Navigating;
/// <summary>
/// Raised when the region is navigated to content.
/// </summary>
event EventHandler<RegionNavigationEventArgs> Navigated;
Looks to me like "all" you need is a reference to your region navigation service in the view.
Handle those two events to set blur then remove blur.
You could then do navigation in code behind or viewmodel. Whichever suits.
If you wanted to decouple viewmodel from view, you could use the eventaggregator.
There is another option though.
You don't explain exactly what you have there. So let's imagine and consider a better way to do this.
Say you have a set content of a set control you're always navigating. That's being switched out as you navigate for a new view whose datacontext is a new viewmodel.
You could bind an attached property from the window to the datacontext of that.
In that property you can have a change callback.
In a base viewmodel you could add an IsLoaded bool property which is initialy false.
When your dependency property callback returns null or false then you blur.
You change the viewmodel property to false in the current viewmodel when you start to navigate. The window blurs. The content is switched out and you get a new viewmodel. Once navigation completes you set that ILoaded true. You callback un blurs the window.

Bug in UWP or am I missing something

I'm starting my first Universal Windows app. First thing I wanted was to subclass the main "Page" class for navigation. For simple purposes, I wanted to just add a RightTapped event hook to display a message of the actual page displayed...
Anyhow, I created a brand new project. Created a single class MyPage
public class MyPage : Page
{
public MyPage()
{
RightTapped += MyPage_RightTapped;
}
private async void MyPage_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
var dialog = new MessageDialog("This page is " + GetType(), "What is my form");
await dialog.ShowAsync();
}
}
Then on the default main form, I changed MainPage.xaml from
<Page
to
<local:MyPage
In the codebehind, I changed
public sealed partial class MainPage : Page
to
public sealed partial class MainPage
Run the form, it works, right-click on keyboard and message comes up.
Now the problem. In the main page, at the Grid declaration, it is define with a background...
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
If I remove this color (the actual color default is "#FFFFFFFF")
<Grid>
the RightTapped even no longer works and this is the ONLY change. I put in a background of any other color and RightTapped works.
Can anyone explain this and why it fails without background color which should not have any bearing vs working with a background?
This sounds like it's documented behavior. The documentation for UIElement.RightTapped contains some relevant hints:
For touch actions and also for interaction-specific or manipulation events that are consequences of a touch action, an element must be hit-test visible in order to be the event source and fire the event that is associated with the action. UIElement.Visibility must be Visible. Other properties of derived types also affect hit-test visibility. For more info, see Events and routed events overview.
And the details from Events and routed events overview: Hit testing and input events:
There are several factors that affect hit testing, but you can determine whether a given element can fire input events by checking its IsHitTestVisible property. This property returns true only if the element meets these criteria:
The element's Visibility property value is Visible.
The element's Background or Fill property value is not null. A nullBrush value results in transparency and hit test invisibility. (To make an element transparent but also hit testable, use a Transparent brush instead of null.)
The Grid's Background property (inherited from Panel) defaults to null, making the Grid without a Background XAML attribute invisible to hit-testing.

Drag/drop controls onto child Canvas without parent Canvas being notified

I'm trying to create a visual "designer" that will allow users to drag controls from a toolbox onto a design canvas. The excellent tutorial here has helped me get a basic system up and going - I can drag controls from the toolbox, select and resize them etc. Amongst other things,the code uses a modified Canvas control, overriding the OnDrop method.
However, I'd like to give the user the option of defining "panels" within the design: effectively smaller Canvas's containing the toolbox controls - as an example:
So when the user drags the button onto Canvas_1, OnDrop fires and all is good. However, if the user creates Canvas_2, and then drags the button onto Canvas_2 - itself a child control of Canvas_1 - the parent OnDrop still fires and the button is added to Canvas_1.
I've tried setting the ZIndex of Canvas_2 to greater than Canvas_1, to no avail - the parent Canvas always gets the event. How can I ensure that Canvas_2 gets the OnDrop events for controls landing on it? Is there a different approach I should be using?
The event DragDrop.DragEnter uses a bubbling routing strategy, that means the event will travel upwards in the visual tree starting from the source element, giving a chance for all the elements along the route to handle the event.
When you drop something in the inner Canvas, it handles it, but doesn't stop the event propagation, so the parent element (the containing Canvas) will handle it again.
In the overriden OnDrop method you have a DragEventArgs parameter.
Set its Handled property to true if you want to prevent containing elements to handle the same event.
Example
I created a simple derived Canvas as well:
public class MyCanvas : Canvas
{
public static readonly DependencyProperty StopDropPropagationProperty = DependencyProperty.Register(
"StopDropPropagation", typeof (bool), typeof (MyCanvas), new PropertyMetadata(default(bool)));
public bool StopDropPropagation
{
get { return (bool) GetValue(StopDropPropagationProperty); }
set { SetValue(StopDropPropagationProperty, value); }
}
protected override void OnDrop(DragEventArgs e)
{
Children.Add(new TextBlock
{
Text = "Dropped here"
});
e.Handled = StopDropPropagation;
}
}
In the view I've put two of these, one nested inside the other.
<local:MyCanvas AllowDrop="True" Background="DarkGray">
<local:MyCanvas Width="300" Height="300" Canvas.Left="100" Canvas.Top="10" Background="BurlyWood"
StopDropPropagation="True"></local:MyCanvas>
</local:MyCanvas>
The thing to note is that I added the new DependencyProperty called StopDropPropagation and set it to true in the inner Canvas. That will set the Handled to true.
So when I drop something in the inner Canvas the TextBlock will only be added in that element:

Prism RequestNavigate and ViewModel OnPropertyChanged

I am using the Telerik RadRibbonView in my WPF 4.5 project. The set up looks like this.
In my Shell I have a RibbonView and a TabControl defined as a regions called “RibbonRegion” and “TabRegion”. The RibbonRegion is basically the menu of the application and the TabRegion holds the main content.
I have also created a module with a View containing a RibbonTab and a RibbonButton. This button is hocked up to a command that sets the DataContext of a RibbonContextTabView and a TabItemView and registers them in their respective regions. The ContextTab and the TabItem is sharing the same ViewModel. This ViewModel has a propery “IsSelected” that the ContextTab and TabItem are bound to.
if (_regionManager.Regions["RibbonRegion"].Views.Any(v => v.GetType() == typeof(ContextTabView)) && _regionManager.Regions["TabRegion"].Views.Any(v => v.GetType == typeof(TabItemView)))
{
_regionManager.RequestNavigate("RibbonRegion", new Uri("ContextTabView", UriKind.Relative));
_regionManager.RequestNavigate("TabRegion", new Uri("TabItemView", UriKind.Relative));
}
else
{
ContextTabView contextTabView = _container.Resolve<ContextTabView>();
TabItemView tabItemView = _container.Resolve<TabItemView>();
contextTabView.DataContext = tabItemView.DataContext = new ContextTabTabItemViewModel();
_regionManager.RegisterViewWithRegion("RibbonRegion", () => contextTabView);
_regionManager.RegisterViewWithRegion("TabRegion", () => tabItemView);
}
The first time the Command above is executed the DataContext of the views is set and then they are registered in the regions. This also sets the “IsSelected” property to true. If I change focus to the RibbonTab my ContextTab and TabItem loses focus and the “IsSelected” propery is set to false. If I press the button again the RequestNavigate is executed and once again the property is set to true. Here is my problem. If I do this a third time nothing happens! The RequestNavigate is executed but the property is not set to true and the Views does not regain focus. I am fairly new to PRISM and I am afraid that I am way off here. Any help would be appreciated.
In order to keep communication between ViewModels in a loosely coupled manner, you could simply use the EventAggregator and raise an event from the Command Button implementation, which would be then handled by the TabItemViewModel.
The solution you mentioned by adding one ViewModel into another would not be ideal as these components would end up working with tight coupling and defining an incorrect situation as Views/ViewModels would not depend on another View.
Therefore, to accomplish the EventAgregation approach, you would need to receive the EventAggregator from the container throw constructor on the View/ViewModel where the button is clicked, and on each one of the ViewModels you would want to subscribe to that event setting the IsSelected property inside the EventHandler method.
You could subscribe to the "GiveFocusEvent" event and handle it on the ViewModels which would set their IsSelected property as shown below:
public TabItemViewModel(IEventAggregator eventAggregator, ..){
...
GiveFocusEvent setFocusEvent = eventAggregator.Get<GiveFocusEvent>();
setFocusEvent.Subscribe(SetFocusEventHandler, ThreadOption.UIThread);
}
public void SetFocusEventHandler(){
// change IsSelected property value..
}
The Event would be published from inside the Button's CommandHandler method as follows:
this.eventAggregator.GetEvent<GiveFocusEvent>().Publish();
Notice that you would need to create and make your "GiveFocusEvent" event class inherit from CompositePresentationEvent:
public class GiveFocusEvent : CompositePresentationEvent<string>{}
I hope this helped you,
Regards.

What is the last event to fire when loading a new WPF/C# window?

I am trying to load a preferences window for my application and I would like the apply button to initially be disabled, then when a preference is updated, the apply button gets enabled again. I have some controls data bound to a preferences object and what happens is that after the window loads, the combobox events get triggered. Is there any event that is guaranteed to happen dead last after everything is stable?
Here is what my code looks like (the apply button is always enabled after the window loads):
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_preferencesData = new PreferencesDataContext();
LayoutRoot.DataContext = _preferencesData;
ButtonApply.IsEnabled = false;
}
private void ComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ButtonApply.IsEnabled = true;
}
Is it also interesting to note that this only happens with textboxes and comboboxes, not checkboxes or radiobuttons.
Best solution for simple need
Joseph's answer is the best solution by far for your simple need: Just use data binding and let the data model handle it.
Answer to question as posed
There are more complex scenarios when you really do need control after absolutely everything has finished loading and all events have fired. There is no single event that occurs "dead last", but it is easy to effectively roll your own using the Dispatcher queue.
This is how to do it:
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>
{
var x = ComputeSomething(1, 2, 3);
DoSomething(x, "Test");
}));
Everything inside the { } will be executed when WPF finishes everything at a higher priority than ContextIdle, which includes all event handlers, loaded events, input events, rendering, etc.
Sequence of events when a Window is created and shown
As requested, here is the sequence of major events in WPF when a window is created and shown:
Constructors and getters/setters are called as objects are created, including PropertyChangedCallback, ValidationCallback, etc on the objects being updated and any objects that inherit from them
As each element gets added to a visual or logical tree its Intialized event is fired, which causes Styles and Triggers to be found applied in addition to any element-specific initialization you may define [note: Initialized event not fired for leaves in a logical tree if there is no PresentationSource (eg Window) at its root]
The window and all non-collapsed Visuals on it are Measured, which causes an ApplyTemplate at each Control, which causes additional object tree construction including more constructors and getters/setters
The window and all non-collapsed Visuals on it are Arranged
The window and its descendants (both logical and visual) receive a Loaded event
Any data bindings that failed when they were first set are retried
The window and its descendants are given an opportunity to render their content visually
Steps 1-2 are done when the Window is created, whether or not it is shown. The other steps generally don't happen until a Window is shown, but they can happen earlier if triggered manually.
The Window.ContentRendered event fulfilled my requirements.
I just did kind of the same thing behaviorly in a systray WPF app.
However, I didn't do it using event handling. I simply bound the Enabled property of my button to a property in my ViewModel, and had the property updated whenever I needed the behavior.
You can use ManagedSpy to figure this out on your own.
http://msdn.microsoft.com/en-us/magazine/cc163617.aspx
Setting the DataContext will likely fire the SelectionChanged event, and you can't rely on when exactly it's fired. Some logic checking on what exactly is selected would be more reliable:
private void ComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (myComboBox.SelectedItem == null)
{
buttonApply.IsEnabled = false;
}
else
{
buttonApply.IsEnabled = true;
}
}
The reason it's happening afterwards with your code as-is is because the event gets queued on the thread for the UI, so it's up to Windows if it will execute the next line of code in Load, or to handle the other events on the queue.
Not to throw a whole lot of stuff at you that you may or may not be familiar with, but if this is a relatively new codebase, you may want to consider using the MVVM pattern and use Commands instead of the archaic (emphasis mine) eventing model.
Order of Events in Windows Forms
Control.HandleCreated
Control.BindingContextChanged
Form.Load
Control.VisibleChanged
Form.Activated
Form.Shown

Categories

Resources