Introduction
I was thinking about possibilities to improve usability and one of my ideas was to "focus" on a control to show the user where s/he can do something.
Using the Code
The usage is pretty simple. After adding a reference to the library, just type some code that looks like this:
var overlay = new Focuser.FocusOverlay<TextBox>(this.Container, this.Test);
overlay.Show();
To hide the overlay on mouseclick
, your code should look like this:
overlay.MouseDown += new MouseButtonEventHandler((sender2, e2) =>
{
overlay.Hide();
this.Test = (TextBox)overlay.CloneBack()
});
The CloneBack
function replaces the original control by the one that is shown in the overlay (which is a clone). You need to call this function if you want to keep the changes made to the control while showing it in the overlay.
Important: If you have a variable keeping a reference to the control, which is usually the case if you have given a name to the control, you need to reassign this variable! Otherwise, the reference is out-of-date.
Background
The overlay is an Adorner
, which you can read more about here.
To use a control in an Adorner
, one has to override several functions from the Adorner
class, which are VisualChildrenCount
, GetVisualChild
, MeasureOverride
and ArangeOverride
. And - you have to set the child using the AddLogicalChild
and AddVisualChild
functions.
The child is not the focused control itself, but a Canvas
containing the control. This is needed to place the control exactly at its original position. The absolute original position can be found out like this:
var pos = this.FocusedControl.TransformToAncestor(this.Target).Transform(new Point());
pos -= new Vector(this.FocusedControlClone.Margin.Left,
this.FocusedControlClone.Margin.Top);
As you can see, the position is found out using the TransformToAncestor
function, which returns a GeneralTransforum
. Then you have to subtract the margin from the position because it is also cloned.
To find out the width and height of the control, its ActualWidth
and ActualHeight
property are used.
The position is updated by a timer to stay up-to-date when the window is resized. You can test this in the demo application.
The control to focus on must be a FrameworkElement
, because this is the class containing the Parent
-property which is needed to replace the original control by the clone.
That only works if the control is contained by a Panel
or ItemsControl
. The code looks like this:
this.Canvas.Children.Remove(this.FocusedControlClone);
if (this.FocusedControl.Parent is Panel)
{
var parent = this.FocusedControl.Parent as Panel;
parent.Children.Remove(this.FocusedControl);
parent.Children.Add(this.FocusedControlClone);
}
else
{
var parent = this.FocusedControl.Parent as ItemsControl;
var index = parent.Items.IndexOf(this.FocusedControl);
parent.Items.Remove(this.FocusedControl);
parent.Items.Insert(index, this.FocusedControlClone);
}
this.FocusedControl = this.FocusedControlClone;
this.FocusedControlClone = null;
Points of Interest
The cloning is done in an interesting way that I actually got from the internet:
if (original == null)
return null;
string s = XamlWriter.Save(original);
StringReader stringReader = new StringReader(s);
XmlReader xmlReader = XmlTextReader.Create(stringReader, new XmlReaderSettings());
return (T)XamlReader.Load(xmlReader);
This might not be very elegant, but it works.. ;-).
History
- 24.09.08: Article created