How to set focus to a control with Caliburn.Micro MVVM - c#

I have a form and I want to set the focus to a text box when certain user actions happen. I know the MVVM way of doing things is to bind to VM properties, however the TextBox does not have a property that will allow this to happen. What's the best way to set the focus from the VM?

I have created an IResult implementation that works quite well for achieving this. You can get the view from the ActionExecutionContext of the IResult, which you can then search (I search by name) for the control you want to focus.
public class GiveFocusByName : ResultBase
{
public GiveFocusByName(string controlToFocus)
{
_controlToFocus = controlToFocus;
}
private string _controlToFocus;
public override void Execute(ActionExecutionContext context)
{
var view = context.View as UserControl;
// add support for further controls here
List<Control> editableControls =
view.GetChildrenByType<Control>(c => c is CheckBox ||
c is TextBox ||
c is Button);
var control = editableControls.SingleOrDefault(c =>
c.Name == _controlToFocus);
if (control != null)
control.Dispatcher.BeginInvoke(() =>
{
control.Focus();
var textBox = control as TextBox;
if (textBox != null)
textBox.Select(textBox.Text.Length, 0);
});
RaiseCompletedEvent();
}
}
I have ommitted some extra code to get the view from the context when the view is a ChildWindow I can provide if you require.
Also GetChildrenByType is an extension method, here is one of many implementations available in the wild:
public static List<T> GetChildrenByType<T>(this UIElement element,
Func<T, bool> condition) where T : UIElement
{
List<T> results = new List<T>();
GetChildrenByType<T>(element, condition, results);
return results;
}
private static void GetChildrenByType<T>(UIElement element,
Func<T, bool> condition, List<T> results) where T : UIElement
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
UIElement child = VisualTreeHelper.GetChild(element, i) as UIElement;
if (child != null)
{
T t = child as T;
if (t != null)
{
if (condition == null)
results.Add(t);
else if (condition(t))
results.Add(t);
}
GetChildrenByType<T>(child, condition, results);
}
}
}
Your action would then be something like the following (invoked in Caliburn.Micro ActionMessage style).
public IEnumerable<IResult> MyAction()
{
// do whatever
yield return new GiveFocusByName("NameOfControlToFocus");
}

There is an easier way.
1º In the ViewModel add property _view as your UserControl
2º You must override OnViewLoaded of your ViewModel and
set _view to View object.
3º Set focus from any method.
public UserControlView _view { get; set; }
protected override void OnViewLoaded(object view)
{
base.OnViewLoaded(view);
_view = (UserControlView)view;
}
public void SetFocus()
{
_view.TextBox1.Focus();
}
I hope help you.

Related

Can't access control state in tabcontrol pages ! C#

I have an issue in my project where I have to use "n" number of Usercontrols with tabcontrol's in them.
I am creating txt files with the name and state of all the controls present in the Usercontrols.
The problem is that I can't seem to access the state of the tabcontrol and all the controls in it.
I use this command that works for all the other controls appart from the tabcontrol one...
Any help would be well appreciated.
sw = new StreamWriter(filename.txt);
foreach (Control crl in theformname.Controls)
{
TabPage tab = new TabPage();
if (crl.GetType() == tab.GetType())
{
sw.WriteLine ("tabcontrol accessed");
if (Ctrl.GetType() == cbx.GetType())
{
CheckBox CheckBoxCrt;
CheckBoxCrt = (CheckBox)Ctrl;
sw.WriteLine(CheckBoxCrt.Checked.ToString()); //State of the checkbox
}
}
}
The following would need one layer above TabControl for your UserControl which would be done by mimicking TabControlList or CheckBoxList
Example
public static List<UserControlType> MyUserControlList(this Control control)
=> control.Descendants<UserControlType>().ToList();
Here are the current methods for use in the next code block.
public static class GenericExtensions
{
public static IEnumerable<T> Descendants<T>(this Control control) where T : class
{
foreach (Control child in control.Controls)
{
if (child is T thisControl)
{
yield return (T)thisControl;
}
if (child.HasChildren)
{
foreach (T descendant in Descendants<T>(child))
{
yield return descendant;
}
}
}
}
public static List<TabControl> TabControlList(this Control control)
=> control.Descendants<TabControl>().ToList();
public static List<CheckBox> CheckBoxList(this Control control)
=> control.Descendants<CheckBox>().ToList();
}
Usage (remember you need to add a list above the TabControl check) using a StringBuilder which can be used to write wherever you want e.g. filename.txt.
StringBuilder builder = new StringBuilder();
var tabs = this.TabControlList();
foreach (TabControl tab in tabs)
{
builder.AppendLine(tab.Name);
foreach (CheckBox box in tab.CheckBoxList())
{
builder.AppendLine($"\t{box.Parent.Name,-20}{box.Name, -20}{box.Checked}");
}
}
Debug.WriteLine(builder);

Adding a control to the main container of unknown object

void Visualize(object CoreObj, object ParentControl)
{
if(CoreObj is typeA)
{
object control1 = new MyControl1(CoreObj);
ParentControl.FirstChild.Children.Add(control1);
foreach (object obj in CoreObj.Children)
{
Visualize(obj, control1);
}
}
else if (CoreObj is typeB)
{
object control2 = new MyControl2(CoreObj);
ParentControl.FirstChild.Children.Add(control2);
foreach (object obj in CoreObj.Children)
{
Visualize(obj, control2);
}
}
}
Where FirstChild is always container, no matter StackPanel, Grid or Canvas, or whatever.
How I get the first child, and the harder part, how to do Children.Add() on object?
I can require something else in case "Children" is inherited from somewhere in all wpf containers, but I can't find out which ancestor/interface contains "Children". Or I can use Reflection probably..
How to do this?
Here's what I came with, finally
interface IContain
{
Panel GetMain(); //return main container
}
// ...
void Visualize(object CoreObj, Panel ParentControl)
{
UIElement control = new UIElement();
if (CoreObj is File) { control = new NameSpacer(); } //new NameSpacer(obj);
else if (CoreObj is Namespace) { control = new NameSpacer(); }
else if(CoreObj is Using) { control = new NameSpacer(); }
if (control.GetType() == typeof(UIElement)) return;
ParentControl.Children.Add(control);
FieldInfo finf = CoreObj.GetType().GetField("Children"); if (finf == null) return;
var val = finf.GetValue(CoreObj); if (val.GetType() != typeof(IEnumerable<object>)) return;
if (control is IContain == false) return;
Panel container = ((IContain)control).GetMain();
foreach (object o in val as IEnumerable<object>)
{
Visualize(o, container);
}
}
You can use VisualTreeHelper class to get the first child.
Children property is defined in abstract class Panel.
var firstChild = parentControl.Descendants().OfType<Panel>().First();
firstChild.Children.Add(control1);
The descendants method leverages VisualTreeHelper in order to get all descendants and you have to define it as extension method:
public static IEnumerable<DependencyObject> Descendants(this DependencyObject element)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < childrenCount; i++)
{
var visualChild = VisualTreeHelper.GetChild(element, i);
yield return visualChild;
foreach (var visualChildren in Descendants(visualChild))
{
yield return visualChildren;
}
}
}
StackPanel, Grid and Canvas, all derive from the Panel class. This is also the class that contains the Children property.
if you know your argument is a Panel, you also have access to Children.First() and Children.Add(..)

Hiding the ellipsis within an AppBar

When you create an AppBar or a CommandBar in a UWP app, there's always an ellipsis hiding near the side of the control, like so:
I don't want it in my app but I haven't found any methods/properties within AppBarthat would help me get rid of it. It should be possible, because many of the default Windows 10 apps don't have it. For example, there's no ellipsis on the main menu bar below:
Is it possible to hide the ellipsis using AppBar, or do I have to use a SplitView or some other control to implement this?
First, try not to use AppBar in your new UWP apps.
The CommandBar control for universal Windows apps has been improved to
provide a superset of AppBar functionality and greater flexibility in
how you can use it in your app. You should use CommandBar for all new
universal Windows apps on Windows 10.
You can read more about it here.
Both CommandBar and AppBar can be full styled and templated. This gives you the ability to remove whatever UI elements you don't want to display.
This is how you do it -
Open your page in Blend, right click on CommandBar > Edit Template > Edit a Copy. Then make sure you select Define in Application as currently there's a bug in Blend which will fail to generate the styles if you choose This document.
Once you have all the styles, find the MoreButton control and set its Visibility to Collapsed (or you can remove it but what if you realise you need it later?).
Then you should have a CommandBar without the ellipsis.
Update for 2017
The visibility of the Ellipsis button can now be found in the OverflowButtonVisibility Property of a CommandBar. As above set it to Collapsed to hide it.
If you want to hide this button globally it enough to add
<Style x:Key="EllipsisButton" TargetType="Button">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
to global resource file
I know this question is is not active any more, but for sake of completion I am proposing my answer.
Instead of changing the visibility by using Styles, I have written an AttachedProperty extension that is able to hide/show the MoreButton via data binding. This way you can show/hide it conditionally as you please.
Usage is as simple as binding your property to the extension:
<CommandBar extensions:CommandBarExtensions.HideMoreButton="{Binding MyBoolean}">
...
</CommandBar>
The extension code is as follows:
public static class CommandBarExtensions
{
public static readonly DependencyProperty HideMoreButtonProperty =
DependencyProperty.RegisterAttached("HideMoreButton", typeof(bool), typeof(CommandBarExtensions),
new PropertyMetadata(false, OnHideMoreButtonChanged));
public static bool GetHideMoreButton(UIElement element)
{
if (element == null) throw new ArgumentNullException(nameof(element));
return (bool)element.GetValue(HideMoreButtonProperty);
}
public static void SetHideMoreButton(UIElement element, bool value)
{
if (element == null) throw new ArgumentNullException(nameof(element));
element.SetValue(HideMoreButtonProperty, value);
}
private static void OnHideMoreButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var commandBar = d as CommandBar;
if (e == null || commandBar == null || e.NewValue == null) return;
var morebutton = commandBar.FindDescendantByName("MoreButton");
if (morebutton != null)
{
var value = GetHideMoreButton(commandBar);
morebutton.Visibility = value ? Visibility.Collapsed : Visibility.Visible;
}
else
{
commandBar.Loaded += CommandBarLoaded;
}
}
private static void CommandBarLoaded(object o, object args)
{
var commandBar = o as CommandBar;
var morebutton = commandBar?.FindDescendantByName("MoreButton");
if (morebutton == null) return;
var value = GetHideMoreButton(commandBar);
morebutton.Visibility = value ? Visibility.Collapsed : Visibility.Visible;
commandBar.Loaded -= CommandBarLoaded;
}
}
On initial binding it uses the Loaded event to apply the hiding once it has been loaded. The FindDescendantByName is another extension method that iterates the visual tree. You might want to create or grab one if your solution does not yet contain it.
Since I cannot add a comment to the particular answer I'll post it here.
The following page gives many examples that will find the child object to compliment #RadiusK's answer.
How can I find WPF controls by name or type?
The one that worked for me specifically in UWP was:
/// <summary>
/// Finds a Child of a given item in the visual tree.
/// </summary>
/// <param name="parent">A direct parent of the queried item.</param>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="childName">x:Name or Name of child. </param>
/// <returns>The first parent item that matches the submitted type parameter.
/// If not matching item can be found,
/// a null parent is being returned.</returns>
public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null)
return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null)
break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
Calling the code like this:
var morebutton = FindChild<Button>(commandBar, "MoreButton");
Building upon #RadiusK's answer (which has some issues), I came up with a conciser alternative that's tested and works:
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
namespace Linq
{
public static class CommandBarExtensions
{
public static readonly DependencyProperty HideMoreButtonProperty = DependencyProperty.RegisterAttached("HideMoreButton", typeof(bool), typeof(CommandBarExtensions), new PropertyMetadata(false, OnHideMoreButtonChanged));
public static bool GetHideMoreButton(CommandBar d)
{
return (bool)d.GetValue(HideMoreButtonProperty);
}
public static void SetHideMoreButton(CommandBar d, bool value)
{
d.SetValue(HideMoreButtonProperty, value);
}
static void OnHideMoreButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var CommandBar = d as CommandBar;
if (CommandBar != null)
{
var MoreButton = CommandBar.GetChild<Button>("MoreButton") as UIElement;
if (MoreButton != null)
{
MoreButton.Visibility = !(e.NewValue as bool) ? Visibility.Visible : Visibility.Collapsed;
}
else CommandBar.Loaded += OnCommandBarLoaded;
}
}
static void OnCommandBarLoaded(object sender, RoutedEventArgs e)
{
var CommandBar = sender as CommandBar;
var MoreButton = CommandBar?.GetChild<Button>("MoreButton") as UIElement;
if (MoreButton != null)
{
MoreButton.Visibility = !(GetHideMoreButton(CommandBar) as bool) ? Visibility.Visible : Visibility.Collapsed;
CommandBar.Loaded -= OnCommandBarLoaded;
}
}
public static T GetChild<T>(this DependencyObject Parent, string Name) where T : DependencyObject
{
if (Parent != null)
{
for (int i = 0, Count = VisualTreeHelper.GetChildrenCount(Parent); i < Count; i++)
{
var Child = VisualTreeHelper.GetChild(Parent, i);
var Result = Child is T && !string.IsNullOrEmpty(Name) && (Child as FrameworkElement)?.Name == Name ? Child as T : Child.GetChild<T>(Name);
if (Result != null)
return Result;
}
}
return null;
}
}
}

Need a better way to Iterate through Form Controls

In my WinForms application written in C# there is a Button on one Form which needs to slightly alter the appearance of a second Form (just change the Text on a Button).
I have managed to do this, but the code is horribly long, and I believe there must be a much more concise way of achieving the same thing.
Here is my code for the Button on Form frmConflicts and how it changes the Text on the Button btnAddCase on Form frmAdmin (works, but seems too long) -
private void btnNoConflicts_Click(object sender, EventArgs e)
{
try
{
foreach (Form f in Application.OpenForms)
{
if (f.Name == "frmAdmin")
{
frmAdmin a = (frmAdmin)f;
a.conflictsClear = true;
foreach (Control ctrl in a.Controls)
{
if (ctrl.Name == "panAdmin")
{
foreach (Control ctrl2 in ctrl.Controls)
{
if (ctrl2.Name == "tabControlAdmin")
{
TabControl tab = (TabControl)ctrl2;
foreach(TabPage page in tab.TabPages)
{
if (page.Name == "pageNewCase")
{
foreach (Control ctrl3 in page.Controls)
{
if (ctrl3.Name == "panCaseDetails")
{
foreach (Control ctrl4 in ctrl3.Controls)
{
if (ctrl4.Name == "btnAddCase")
{
ctrl4.Text = "Add Case";
}
}
}
}
}
}
}
}
}
}
}
}
this.Close();
}
catch (Exception eX)
{
MessageBox.Show("frmConflicts: btnNoConflicts()" + Environment.NewLine + eX.Message);
}
Any help to significantly reduce the amount of code would be much appreciated, as I am going to need to do similar interactions between Forms elsewhere in my application.
If your button is added through designer and is not dynamically created the solution is simple: add a method inside your frmAdmin like
public void ChangeCaption(string caption)
{
btnAddCase.Text = "Add case";
}
and then
var frmAdmin = Application.OpenForms.OfType<Form>().FirstOrDefault(x => x.GetType() == typeof(frmAdmin));
if (frmAdmin != null)
{
frmAdmin.ChangeCaption("Add case");
}
I think it`s help to you
foreach (Form f in Application.OpenForms)
{
var controls =this.Controls.Find("btnAddCase", true);
if(controls!=null)
foreach(var control in controls)
{
control.Text="Add case";
}
}
If the the appearance of second from require a change on first from you should solve this in another way.
The best is that your button that require a change should be open for capture the event of form two open and then apply required change.
In the place where you declare your button you should assign to it a listener that will capture the Form2 opening and then apply action.
so in the method private void btnNoConflicts_Click(object sender, EventArgs e) you should trigger event for that button to capture instead off searching it.
You could use LINQ + ControlCollection.Find:
Control btnAddCase = Application.OpenForms.Cast<Form>()
.Where(f => f.Name == "frmAdmin")
.SelectMany(f => f.Controls.Find("btnAddCase", true)) // true means recursive
.FirstOrDefault();
if(btnAddCase != null)
btnAddCase.Text = "Add Case";
You could create a public property and subscribe to a PropertyChanged event from your form, you'll need your class that has the public variable to extend INotifyPropertyChanged.
//Your class
public class ButtonText : INotifyPropertyChanged
{
private string _buttonText;
public string ButtonValue
{
get{ return _buttonText; }
set
{
//Sets the value of _buttonText to the value passed in an argument
_buttonText = value;
RaisePropertyChanged("ButtonValue");
}
}
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
In your form class you'd bind to the property ButtonValue property of the ButtonText class like so:
ButtonText buttonObj = new ButtonText();
//Property field to bind, object to bind, property to bind
btnAddCase.DataBindings.Add("Text", buttonObj,"ButtonValue");
buttonObj.ButtonText = "Your text to bind.";
Because the btnAddCase.Text property is bound to the ButtonValue property of the ButtonText class, your btnAddCase.Text property will reflect the value of your ButtonText.ButtonValue property at all times, it's also a two way binding.

Get control by name, including children

I'm trying to get a control by name. I wrote the following code:
public Control GetControlByName(string name)
{
Control currentControl;
for(int i = 0,count = Controls.Count; i < count; i++)
{
currentControl = Controls[i];
if (currentControl.HasChildren)
{
while (currentControl.HasChildren)
{
for(int x = 0,size = currentControl.Controls.Count; x < size; x++)
{
currentControl = currentControl.Controls[x];
if (currentControl.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
{
return currentControl;
}
}
}
}
else
{
if (currentControl.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
{
return currentControl;
}
}
}
return null;
}
It only ever returns null. Can someone point out my mistake? Any help or ways to improve this code are welcomed.
Just use the Controls collection Find method:
var aoControls = this.Controls.Find("MyControlName", true);
if ((aoControls != null) && (aoControls.Length != 0))
{
Control foundControl = aoControls[0];
}
I actually wrote some extension methods at work to do just this thing:
public static class MyExensions ()
{
public static Control FindControlRecursively (this Control control, string name)
{
Control result = null;
if (control.ID.Equals (name))
{
result = control;
}
else
{
foreach (var child in control.Children)
{
result = child.FindControlRecursively (name);
if (result != null)
{
break;
}
}
}
return result;
}
public static T FindControlRecursively<T> (this Control control, string name)
where T: Control
{
return control.FindControlRecursively (name) as T;
}
}
Note: Null checks removed for the sake of simplicity.
You can use it to find, say, a TextBox on your form like so:
public class MyForm : Form
{
public void SetSomeText ()
{
var control = this.FindControlRecursively<TextBox> ("myTextboxName");
if (control != null)
{
control.Text = "I found it!";
}
// Or...
var control2 = this.FindControlRecursively ("myTextboxName2") as TextBox;
if (control != null)
{
control2.Text = "I found this one, also!";
}
}
}
Edit
Of course, this is a depth-first algorithm, which might be slow depending on how deep your control chain is. You might rewrite it to use a breadth-first algorithm if you see that it is too slow.
A slight tweak for if you're not using System.Windows.Forms (this is what .Find(string, bool) works off too)
public static class MyExensions
{
public static Control FindControlRecursively(this Control control, string name)
{
Control result = null;
if (control.ID.Equals(name))
{
result = control;
}
else
{
for (var i = 0; i < control.Controls.Count; i++)
{
result = control.Controls[i].FindControlRecursively(name);
if (result != null)
{
break;
}
}
}
return result;
}
public static T FindControlRecursively<T>(this Control control, string name)
where T : Control
{
return control.FindControlRecursively(name) as T;
}
}
p.s. Yes I know it's an old question, but in case it helps 😁

Categories

Resources