Can I "click" a WinUI 3 Button from code? - c#

I would like to programmatically cause a Click event to fire for a Button in a WinUI 3 desktop app. I've seen lots of questions about how to deal with this on SO but the answers all involve invoking the handler (using commands or direct calls, etc.), instead of through firing the event on the Button.
This is a great choice if you actually have access to the handler, but what if you don't? What if the Button is embedded in a more complex control and the handler is internal and does some non-trivial (or unknown) additional processing?
For example, I recently tried to reformat the DatePicker control (which seems to have a mind of its own about size and placement of its constituent TextBlocks and LoopingSelectors). I tried rewriting Styles and deriving Controls, worked with MeasureOverride and ArrangeOverride - all the common approaches - but nothing worked. DatePicker clearly does formatting in code. I finally just hid the Button (FlyoutButton) DatePicker defines and added my own. But now I'd like my Button (ReplacementFlyoutButton) to invoke the same handler the original FlyoutButton did, thus creating the proper Flyout and doing all the date localization, etc. for me. Overriding OnApplyTemplate in a derived class gives me access to the underlying FlyoutButton (in DatePicker):
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Tree looks like:
// this as DatePicker
// LayoutRoot as Grid
// HeaderContentPresenter as ContentPresenter
// FlyoutButton as Button
// FlyoutButtonGrid as Grid
// ContentPresenter as ContentPresenter
// FlyoutButtonContentGrid as Grid
// DayTextBlock as TextBlock
// MonthTextBlock as TextBlock
// YearTextBlock as TextBlock
// FirstPickerSpacing as Rectangle
// SecondPickerSpacing as Rectangle
DayTextBlock = GetTemplateChild("DayTextBlock") as TextBlock;
MonthTextBlock = GetTemplateChild("MonthTextBlock") as TextBlock;
YearTextBlock = GetTemplateChild("YearTextBlock") as TextBlock;
FirstPickerSpacing = GetTemplateChild("FirstPickerSpacing") as Rectangle;
SecondPickerSpacing = GetTemplateChild("SecondPickerSpacing") as Rectangle;
FlyoutButtonContentGrid = GetTemplateChild("FlyoutButtonContentGrid") as Grid;
FlyoutButton = GetTemplateChild("FlyoutButton") as Button;
_replacementFlyoutButton = GetTemplateChild("ReplacementFlyoutButton") as Button;
if (_replacementFlyoutButton is not null) { _replacementFlyoutButton.Click += OnReplacementFlyoutButtonClick;}
}
Is there any way to invoke the Click event on DatePicker or its FlyoutButton in OnReplacementFlyoutButtonClick()?
Thanks for any ideas.
(Just as a note, neither the DatePicker nor the FlyoutButton have a non-null ContextFlyout and FlyoutButton's Flyout is null.)

All credit goes to #SimonMourier who pointed out that Microsoft UI Automation makes this easy - thank you Simon.
I was unable to find a good, well structured introduction to the use of UI Automation in WinUI 3 but, after looking over the documentation for the Microsoft.UI.Xaml.Automation.*, and particularly Microsoft.UI.Xaml.Automation.Peers, namespaces I realized that WinUI 3 Controls already implement classes that expose them to Microsoft UI Automation. For my question, the relevant class is Microsoft.UI.Xaml.Automation.Peers.ButtonAutomationPeer (please note the namespace for WinUI 3).
The modified code:
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
_replacementFlyoutButton = GetTemplateChild("ReplacementFlyoutButton") as Button
?? throw new ArgumentException("Missing ReplacementFlyoutButton in DatePickerEx");
var FlyoutButton = GetTemplateChild("FlyoutButton") as Button
?? throw new ArgumentException("Missing FlyoutButton in DatePickerEx");
_replacementButtonAutomationPeer = FrameworkElementAutomationPeer.FromElement(FlyoutButton) as ButtonAutomationPeer
?? throw new ArgumentException("Missing ButtonAutomationPeer in DatePickerEx");
_replacementFlyoutButton.Click += OnReplacementFlyoutButtonClick;
}
private void OnReplacementFlyoutButtonClick(object sender, RoutedEventArgs e)
=> _replacementButtonAutomationPeer.Invoke();
Clicking the ReplacementFlyoutButton will now Invoke the FlyoutButton in the OnReplacementFlyoutButtonClick event handler, as required.

Related

Focus abstraction

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.

Fire event from ManWindow in selected tab

I'm sorry if this question was asked before.
After 1 hour of searching here i could not to find it.
First, i'm using WPF, but not with MVVM. I know MVVM is way to go and i'm learning it. I'm new to programming.
It's small program and i have buttons on mainwindow in one StackPanel, and TabControl (_tabcntrl) in another.
On button click mainwindow generates one tab:
TabItem _tab = new TabItem();
UserControl _uc = new UserControl();
_tab.Content = _uc;
_tabcntrl.Items.Add(tab);
In usercontrol i have one public event
public void test()
{
//some code
}
So my question is how to fire this event from main window (button click in main window), but only in selected tab. Idea is that you can have multiple tabs with same usercontrol.
I know i can do it with
_uc.test();
But only when tab is created.
Also i tried to put
TabItem tb = _tabcntrl.SelectedItem;
tb.test();
In button click event, but i get error.
Stupid thing is that i figured out how to fire event from usercontrol, and i can't other way around. Feeling pretty stupid for asking this in first place.
Thank You, sorry for my bad English
There are various options, you can either look for VisualTree or simply use Children property of the Control and find respective element.
TabItem tb = _tabcntrl.SelectedItem;
var childControls = control.Children.OfType<UserControl>(); // your controltype
// I'm looping through all child controls of type 'UserControl' but you can customize to your case.
foreach(var control in childControls)
{
// execute control logic here
control.test();
}

user control that other developers can add controls to it and "inherit" the behavior of all controls on my control

hi all and sorry for the confusing title... can't find the right words yet.
just out of curiosity, I am playing with c# user control, I created a control that is built from a panel, a textbox that is being used as a filter, and some labels / buttons/ etc... that are being filtered.
when ever you change the text of the textbox, all the controls on the panel are visible / invisible depending if their Text property contains the Text of the textbox. very simple.
but I want this user control to be such that the user that uses it can drop more labels or controls to it and they will behave the same, I can't figure out how to do that..
when I am editing the control (adding controls to it), it works as expected and the new controls behave as the old ones without code modifications, but only when I am editing the user control and not when using it.
when I am dragging the user control to a form, I can not add controls to it... when I try to add a label to the control - it is just added to the form and not to the control and therefore the text box is not influencing the added label. what should I do if I want to be able to add the control to a form and then add some controls to the control?
I will be happy for some pointers.
here is the relevant code:
private void textBox1_TextChanged(object sender, EventArgs e)
{
foreach (Control c in panel1.Controls)
{
if (c.Text.Contains(textBox1.Text))
{
c.Visible = true;
}
else
{
c.Visible = false;
}
}
}
edit - pictures added.
as you can see - i typed 1 in the filter text box and all the controls except button1 are now invisible - and of course the bad behaving label.
Thanks,
Jim.
This problem can be solved easily by following the guidelines in
https://support.microsoft.com/en-us/kb/813450 which decribes step by step How to make a UserControl object acts as a control container design-time by using Visual C#
In order to modify the user control as a design time control container
add the following code to the Declarations section:
using System.ComponentModel.Design;
Apply the System.ComponentModel.DesignerAttribute attribute to the control as follows:
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))]
public class UserControl1 : System.Windows.Forms.UserControl
{
...
}
Then build the solution.
the control will appear as usual in the Toolbox and can be added to forms. Additional controls such as buttone , text boxes etc.. can be added to the control as required.
You describe one of the reasons why I almost never use UserControls. Anything that isn't done to the original UC must be done in code..
You can instead make it a class that is not a UserControl, ie make it a simple subclass of Panel (or FlowLayoutPanel as I do here merely for convenience, while dropping stuff on it during my tests).
class FilterPanel : FlowLayoutPanel
{
TextBox tb_filterBox { get; set; }
Label st_filterLabel { get; set; }
public FilterPanel()
{
st_filterLabel = new Label();
st_filterLabel.Text = "Filter:";
this.Controls.Add(st_filterLabel);
tb_filterBox = new TextBox();
this.Controls.Add(tb_filterBox);
// st_filterLabel.Location = new Point(10, 10); // not needed for a FLP
// tb_filterBox.Location = new Point(100, 10); // use it for a Panel!
tb_filterBox.TextChanged += tb_filterBox_TextChanged;
}
void tb_filterBox_TextChanged(object sender, EventArgs e)
{
foreach(Control ctl in this.Controls)
{
if (ctl != tb_filterBox && ctl != st_filterLabel)
ctl.Visible = ctl.Text.Contains(tb_filterBox.Text);
}
}
}
Now after placing it on a form (or whatever) you (or whoever) can drop Controls onto it in the designer and they'll be part of its Controls collection, just like you want it and will behave as expected..
Two notes on subclassing Controls:
If you break one during developement, the Form(s) using it will be broken, too, until you fix the problem. So take a little extra care!
For the Designer to display the Control it always needs to have one parameterless constructor, like the one above. Even if you prefer to have the ability to hand in parameters, one parameterless constructor must still be there or the designer will get into trouble!

Add overlay to Window at runtime

I'm writing a simple "tutorial" library that will allow developers to easily add step-by-step tutorials to their existing WPF applications. The tutorials will help first time users of the application find their way around by adding an overlay that highlights a control and explains its purpose. The end result will look something like this:
The regular application:
The overlay explaining the purpose of a control:
My question is this: What's the most reliable and unobtrusive way to inject the overlay view into the current window? The best I've come up with so far is to require the developer to add an attached property to whatever window will be hosting the overlay, and then add the necessary elements on the window's Initialized callback:
public static void IsTutorialOverlayCompatibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((Boolean)e.NewValue == true)
{
if (sender as Window != null)
{
Window window = (Window)sender;
window.Loaded += new RoutedEventHandler((o, eargs) =>
{
Grid newRootElement = new Grid();
newRootElement.Name = "HelpOverlayRoot";
if (window.Content as UIElement != null)
{
UIElement currentContent = (UIElement)window.Content;
window.Content = null;
newRootElement.Children.Add(currentContent);
newRootElement.Children.Add(new HelpOverlayControl());
window.Content = newRootElement;
}
});
}
}
}
This feels like a hack, however, and I'm not sure that there isn't some edge case where this method will break the layout of the application. In addition, it requires that the window's Content property be an instance of type UIElement.
I'd like to avoid forcing developers to change their XAML (i.e, adding a custom overlay UserControl to every window) in order to use my library. What's the best way to add this kind of functionality to an existing WPF application?

Best practice for creating bordered control in WinForm

I am writing WinForm application in C# .NET and I need to add dashed/dotted or any other type of border to any of UI components of application when the user clicks on it. I would like to get something like WinForm GUI editor in Visual Studio.
I am new in .NET so I don't know well what is possible via native methods and properties and what I need to implement myself. I have tried to find something on the net and here but I am not sure what to search, there are different approaches. For example it is possible to draw the border artificially, I mean using graphics. But I guess there should be easier approach.
What can you advice? What is the best practice in this situation? Please provide some portions of code.
Every Control has a Paint event. You have to subscribe to this event and look into the given arguments. The sender is the current control that should be painted. You can cast it within your method to Control. Now you can check the control if it focused by checking control.Focused and if it is true simply do whatever you like within the graphics object of the PaintEventArgs. This can furthermore be encapsulated in an extension method which would make the usage fairly easy.
public static void DrawBorderOnFocused(this Control control)
{
if(control == null) throw new ArgumentNullException("control");
control.Paint += OnControlPaint;
}
public static void OnControlPaint(object sender, PaintEventArgs e)
{
var control = (Control)sender;
if(control.Focused)
{
var graphics = e.Graphics;
var bounds = e.Graphics.ClipBounds;
// ToDo: Draw the desired shape above the current control
graphics.DrawLine(Pens.BurlyWood, new PointF(bounds.Left, bounds.Top), new PointF(bounds.Bottom, bounds.Right));
}
}
The usage within the code would then be something like:
public MyClass()
{
InitializeComponent();
textBox1.DrawBorderOnFocused();
textBox2.DrawBorderOnFocused();
}

Categories

Resources