OnMouseEnter for all controls on a form - c#

I have OnMouseEnter and OnMouseLeave event handlers setup for my form. When the mouse moves over the form I want to set the opacity to 100% and when it moves away I want to set it to 25%. It works well, except when the mouse moves over one of the buttons on the form. The OnMouseLeave event fires and hides the form again. Is there a good way to handle this, without having to wire up OnMouseEnter for every control on the form?

EDIT: I'm going to leave this answer here, even though it can't be made to work reliably. The reason: to prevent somebody else from trying the same thing. See end of message for the reason it won't work.
You can do this fairly easily for the client rectangle by getting the cursor position and checking to see if it's within the Form's client area:
private void Form1_MouseLeave(object sender, EventArgs e)
{
Point clientPos = PointToClient(Cursor.Position);
if (!ClientRectangle.Contains(clientPos))
{
this.Opacity = 0.25;
}
}
This assumes that none of your child controls will be changing the opacity.
However, you'll find that it's a less than perfect solution, because when the mouse goes to the title bar, the Form goes to 0.25%. You could fix that by checking to see if the mouse position is within the window rect (using the Bounds property), but then your window will remain opaque if the mouse moves off the title bar and out of the window.
You have a similar problem when entering the title bar from outside.
I think you'll have to handle the WM_NCMOUSEENTER and WM_NCMOUSELEAVE messages in order to make this work reliably.
Why it can't work:
Even handling the non-client area notifications can fail. It's possible for the mouse to enter on a child control, which would prevent the Form from being notified.

I think it is impossible to do, without handling the MouseEnter and MouseLeave events of all the children, but you do not have to wire them manually.
Here is some code I copied & pasted from a project of mine. It does almost what you described here. I actually copied the idea and the framework from this site.
In the constructor I call the AttachMouseOnChildren() to attach the events.
The OnContainerEnter and OnContainerLeave are used to handle the mouse entering/leaving the form itself.
#region MouseEnter & Leave
private bool _childControlsAttached = false;
/// <summary>
/// Attach enter & leave events to child controls (recursive), this is needed for the ContainerEnter &
/// ContainerLeave methods.
/// </summary>
private void AttachMouseOnChildren() {
if (_childControlsAttached) {
return;
}
this.AttachMouseOnChildren(this.Controls);
_childControlsAttached = true;
}
/// <summary>
/// Attach the enter & leave events on a specific controls collection. The attachment
/// is recursive.
/// </summary>
/// <param name="controls">The collection of child controls</param>
private void AttachMouseOnChildren(System.Collections.IEnumerable controls) {
foreach (Control item in controls) {
item.MouseLeave += new EventHandler(item_MouseLeave);
item.MouseEnter += new EventHandler(item_MouseEnter);
this.AttachMouseOnChildren(item.Controls);
}
}
/// <summary>
/// Will be called by a MouseEnter event, with any of the controls within this
/// </summary>
void item_MouseEnter(object sender, EventArgs e) {
this.OnMouseEnter(e);
}
/// <summary>
/// Will be called by a MouseLeave event, with any of the controls within this
/// </summary>
void item_MouseLeave(object sender, EventArgs e) {
this.OnMouseLeave(e);
}
/// <summary>
/// Flag if the mouse is "entered" in this control, or any of its children
/// </summary>
private bool _containsMouse = false;
/// <summary>
/// Is called when the mouse entered the Form, or any of its children without entering
/// the form itself first.
/// </summary>
protected void OnContainerEnter(EventArgs e) {
// No longer transparent
this.Opacity = 1;
}
/// <summary>
/// Is called when the mouse leaves the form. When the mouse leaves the form via one of
/// its children, this will also call OnContainerLeave
/// </summary>
/// <param name="e"></param>
protected void OnContainerLeave(EventArgs e) {
this.Opacity = DEFAULT_OPACITY;
}
/// <summary>
/// <para>Is called when a MouseLeave occurs on this form, or any of its children</para>
/// <para>Calculates if OnContainerLeave should be called</para>
/// </summary>
protected override void OnMouseLeave(EventArgs e) {
Point clientMouse = PointToClient(Control.MousePosition);
if (!ClientRectangle.Contains(clientMouse)) {
this._containsMouse = false;
OnContainerLeave(e);
}
}
/// <summary>
/// <para>Is called when a MouseEnter occurs on this form, or any of its children</para>
/// <para>Calculates if OnContainerEnter should be called</para>
/// </summary>
protected override void OnMouseEnter(EventArgs e) {
if (!this._containsMouse) {
_containsMouse = true;
OnContainerEnter(e);
}
}
#endregion

I think one way to reliably handle the mouse events you're interested is to set up an IMessageFilter on your Application object from which you can intercept all mouse messages (WM_MOUSEMOVE etc ..) even if they are sent to child controls of the form.
Here's some demo code:
using System;
using System.Windows.Forms;
namespace Test
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
public static Form frm = null;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
frm = new Form1 {Opacity = 0.25};
frm.Controls.Add(new Button{Dock = DockStyle.Fill, Text = "Ok"});
Application.AddMessageFilter(new MouseMoveFilter());
Application.Run(frm);
}
}
public class MouseMoveFilter : IMessageFilter
{
#region IMessageFilter Members
private const int WM_MOUSELEAVE = 0x02A3;
private const int WM_NCMOUSEMOVE = 0x0A0;
private const int WM_MOUSEMOVE = 0x0200;
private const int WM_NCMOUSELEAVE = 0x2A2;
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_NCMOUSEMOVE:
case WM_MOUSEMOVE:
Program.frm.Opacity = 1;
break;
case WM_NCMOUSELEAVE:
case WM_MOUSELEAVE:
if (!Program.frm.Bounds.Contains(Control.MousePosition))
Program.frm.Opacity = 0.25;
break;
}
return false;
}
#endregion
}
}
Alternatively you can inherit from Form class and override PreProcessMessage() to accomplish the same thing ...

Related

Menu Item not showing in Xamarin.Forms after navigation back

I'm developing a Xamarin.Forms Android-App where on a ContentPage a CustomRenderer is being used to display a SearchView in the Header of the Page. The CustomRenderer for the "SearchPage" class looks like the following:
/// <summary>
/// The search page renderer.
/// </summary>
public class SearchPageRenderer : PageRenderer
{
/// <summary>
/// Gets or sets the search view.
/// </summary>
private SearchView searchView;
/// <summary>
/// Gets or sets the toolbar.
/// </summary>
private Toolbar toolbar;
/// <summary>
/// Reaction on the disposing of the page.
/// </summary>
/// <param name="disposing">A value indicating whether disposing.</param>
protected override void Dispose(bool disposing)
{
if (this.searchView != null)
{
this.searchView.QueryTextChange -= this.OnQueryTextChangeSearchView;
}
this.toolbar?.Menu?.RemoveItem(Resource.Menu.mainmenu);
base.Dispose(disposing);
}
/// <summary>
/// Reaction on the element changed event.
/// </summary>
/// <param name="e">The event argument.</param>
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
if (e?.NewElement == null || e.OldElement != null)
{
return;
}
this.AddSearchToToolBar();
}
/// <summary>
/// Adds a search item to the toolbar.
/// </summary>
private void AddSearchToToolBar()
{
this.toolbar = (CrossCurrentActivity.Current?.Activity as MainActivity)?.FindViewById<Toolbar>(Resource.Id.toolbar);
if (this.toolbar != null)
{
this.toolbar.Title = this.Element.Title;
this.toolbar.InflateMenu(Resource.Menu.mainmenu);
this.searchView = this.toolbar.Menu?.FindItem(Resource.Id.action_search)?.ActionView?.JavaCast<SearchView>();
if (this.searchView != null)
{
this.searchView.QueryTextChange += this.OnQueryTextChangeSearchView;
this.searchView.ImeOptions = (int)ImeAction.Search;
this.searchView.MaxWidth = int.MaxValue;
this.searchView.SetBackgroundResource(Resource.Drawable.textfield_search_holo_light);
}
}
}
/// <summary>
/// Reaction on the text change event of the searchbar.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event argument.</param>
private void OnQueryTextChangeSearchView(object sender, SearchView.QueryTextChangeEventArgs e)
{
var searchPage = this.Element as SearchPage;
searchPage?.SearchCommand?.Execute(e?.NewText);
}
}
Thanks to this Stack-Overflow Thread, i got it working like a charm so far.
Now, if the user taps on a item in the "SearchPage", a new ContentPage (called "DetailPage") is pushed to the NavigationStack using the following method:
private async Task PushPageAsync(object model, ContentPage page, INavigation navigation)
{
page.BindingContext = model;
await navigation.PushAsync(page).ConfigureAwait(false);
}
That works without problems. But if the users navigates from the "DetailPage" back to the "SearchPage" by using the Back-Button, the customized Search-Header isn't showing at all.
What I tried:
Using the "OnAppearing" event of the page instead of the "OnElementChanged". That didn't solve the problem at first sight. However, if I add a Task.Delay(500) to the OnAppearing-Method and then add the SearchView again, it is displayed. But this fix seems quite ugly, and if i sleep the app and resume while using the SearchPage, the Search-Widget is showing twice.
So my question is:
Is there a bug in Xamarin or am I doing something wrong?
Is there a bug in Xamarin or am I doing something wrong?
I can't say it's a bug, I prefer to consider it is as by design. The real problem is that you're trying to modify the Toolbar in your custom renderer, and based on your description, you're using NavigationPage, its NavigationPageRenderer will update the view of Toolbar each time when current page is changed.
So you're doing right to use the "OnAppearing" event of the page instead of the "OnElementChanged", which cause another problem, the updating of Toolbar is be delayed when the old page is removed as you can see from the source code in RemovePage method, while the OnAppearing method will be executed immediately when the new page is shown:
Device.StartTimer(TimeSpan.FromMilliseconds(10), () =>
{
UpdateToolbar();
return false;
});
So I don't think you're doing anything wrong too, the method you used await Task.Delay(500); can quickly solve this issue.
Based on your description here:
But this fix seems quite ugly, and if i sleep the app and resume while using the SearchPage, the Search-Widget is showing twice.
I suggest to change your SearchPage to a View, and then dynamically add/remove this view from pages:
public class SearchPage : View
{
...
}
and renderer for it (other codes are the same, only change to inherit from ViewRenderer and the parameter of OnElementChanged is different):
public class SearchPageRenderer : ViewRenderer
{
private SearchView searchView;
/// <summary>
/// Gets or sets the toolbar.
/// </summary>
private Android.Support.V7.Widget.Toolbar toolbar;
...
/// <summary>
/// Reaction on the element changed event.
/// </summary>
/// <param name="e">The event argument.</param>
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
{
base.OnElementChanged(e);
if (e?.NewElement == null || e.OldElement != null)
{
return;
}
this.AddSearchToToolBar();
}
...
}
Then you can use it as a control/view in pages where you want to show it, not directly as a Page. For example, I want to show this search view in my MainPage, then navigate to MainPage in App.xaml.cs:
MainPage = new NavigationPage(new MainPage());
MainPage's layout:
<StackLayout>
...
</StackLayout>
At last override the OnAppearing and OnDisappearing method of the MainPage:
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(500);
var content = this.Content as StackLayout;
content.Children.Add(new SearchPage());
}
protected override void OnDisappearing()
{
base.OnDisappearing();
var content = this.Content as StackLayout;
foreach (var child in content.Children)
{
var searchpage = child as SearchPage;
if (searchpage != null)
{
content.Children.Remove(searchpage);
return;
}
}
}
By the way, if you want to navigate from MainPage here to other pages, you can code like this:
this.Navigation.PushAsync(new Page1());
There is no need to create a PushPageAsync task for it.

Can I have a Checked state on a ToolStripSplitButton in WinForms?

Is there a way to have a Checked state on a ToolStripSplitButton? I want to have a ToolStripButton with a Checked property to determine if an overlay is shown in my application's window. My users want to have direct interaction to control the opacity of the overlay, and to do that I imagine having a split button where the secondary button opens a slider bar to control the opacity. (That of course isn't standard functionality, but I'm confident of being able to put that together).
However, the ToolStripSplitButton doesn't have a Checked property, so I can't do that directly. I tried to have a standard ToolStripButton with a separate ToolStripSplitButton next to it, with the DropDownButtonWidth = 11, and Margin = -5, 1, 0, 2, so that it looks like it belongs to the ToolStripButton. It looks fine, but to complete the illusion of them being a single button, I need to get them to both to draw with the same VisualStyles.PushButtonState - so they both go Hot when the mouse is over either of them.
First I tried using the MouseEnter and MouseLeave events on both buttons to try to change the background color of the other button, but that had no effect. Then I tried to use the Paint event of each button to try to render them with the ButtonRenderer.DrawButton with a PushButtonState = Hot, but that didn't seem to be working either and was becoming very messy. It seems like there must be a better way!
Is there a simple, or at least a practical solution to this?
Edit: The effect I am after is something like this:
I suggest you to create your own UserControl and to use it as a ToolStripItem.
toolStripMenuItem.DropDownItems.Add(new ToolStripControlHost(new OverlayControl()));
Here is a simple example of the OverlayControl, which is deduced from your description (I suggest you to create a real UserControl with the Designer).
public class OverlayControl : UserControl
{
private readonly CheckBox _checkBox = new CheckBox();
private readonly TrackBar _trackBar = new TrackBar();
public ToolStripExtended()
{
_checkBox.Text = "Overlay";
BackColor = SystemColors.Window;
_checkBox.AutoSize = true;
_trackBar.AutoSize = true;
var flowLayoutPanel = new FlowLayoutPanel {AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink};
flowLayoutPanel.Controls.AddRange( new Control[] { _checkBox, _trackBar });
Controls.Add(flowLayoutPanel);
AutoSize = true;
AutoSizeMode = AutoSizeMode.GrowAndShrink;
PerformLayout();
}
}
The solution that I came up with was to create my own button by inheriting from ToolStripSplitButton, add my own Checked property, and then manually draw over it when it is checked:
/// <summary>
/// ToolStripSplitCheckButton adds a Check property to a ToolStripSplitButton.
/// </summary>
public partial class ToolStripSplitCheckButton : ToolStripSplitButton
{
//==============================================================================
// Inner class: ToolBarButonSplitCheckButtonEventArgs
//==============================================================================
/// <summary>
/// The event args for the check button click event. To be able to use the OnCheckedChanged
/// event, we must also record a dummy button as well as this one (hack).
/// </summary>
public class ToolBarButonSplitCheckButtonEventArgs : ToolBarButtonClickEventArgs
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="split_button">The sender split check button</param>
public ToolBarButonSplitCheckButtonEventArgs(ToolStripSplitCheckButton split_button)
: base(new ToolBarButton("Dummy Button")) // Hack - Dummy Button is not used
{
SplitCheckButton = split_button;
}
/// <summary>
/// The ToolStripSplitCheckButton to be sent as an argument.
/// </summary>
public ToolStripSplitCheckButton SplitCheckButton { get; set; }
}
//==========================================================================
// Construction
public ToolStripSplitCheckButton()
{
m_checked = false;
m_mouse_over = false;
}
//==========================================================================
// Properties
/// <summary>
/// Indicates whether the button should toggle its Checked state on click.
/// </summary>
[Category("Behavior"),
Description("Indicates whether the item should toggle its selected state when clicked."),
DefaultValue(true)]
public bool CheckOnClick { get; set; }
/// <summary>
/// Indictates the Checked state of the button.
/// </summary>
[Category("Behavior"),
Description("Indicates whether the ToolStripSplitCheckButton is pressed in or not pressed in."),
DefaultValue(false)]
public bool Checked { get { return m_checked; } set { m_checked = value; } }
//==========================================================================
// Methods
/// <summary>
/// Toggle the click state on button click.
/// </summary>
protected override void OnButtonClick(EventArgs e)
{
if (CheckOnClick) {
m_checked = !m_checked;
// Raise the OnCheckStateChanged event when the button is clicked
if (OnCheckChanged != null) {
ToolBarButonSplitCheckButtonEventArgs args = new ToolBarButonSplitCheckButtonEventArgs(this);
OnCheckChanged(this, args);
}
}
base.OnButtonClick(e);
}
/// <summary>
/// On mouse enter, record that we are over the button.
/// </summary>
protected override void OnMouseEnter(EventArgs e)
{
m_mouse_over = true;
base.OnMouseEnter(e);
this.Invalidate();
}
/// <summary>
/// On mouse leave, record that we are no longer over the button.
/// </summary>
protected override void OnMouseLeave(EventArgs e)
{
m_mouse_over = false;
base.OnMouseLeave(e);
this.Invalidate();
}
/// <summary>
/// Paint the check highlight when required.
/// </summary>
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (m_checked) {
// I can't get the check + mouse over to render properly, so just give the button a colour fill - Hack
if (m_mouse_over) {
using (Brush brush = new SolidBrush(Color.FromArgb(64, SystemColors.MenuHighlight))) {
e.Graphics.FillRectangle(brush, ButtonBounds);
}
}
ControlPaint.DrawBorder(
e.Graphics,
e.ClipRectangle, // To draw around the button + drop-down
//this.ButtonBounds, // To draw only around the button
SystemColors.MenuHighlight,
ButtonBorderStyle.Solid);
}
}
//==========================================================================
// Member Variables
// The delegate that acts as a signature for the function that is ultimately called
// when the OnCheckChanged event is triggered.
public delegate void SplitCheckButtonEventHandler(object source, EventArgs e);
public event SplitCheckButtonEventHandler OnCheckChanged;
private bool m_checked;
private bool m_mouse_over;
}
To use this, handle the OnCheckChanged event for this button.
This has a couple of clunky hacks, however. To be able to raise an OnCheckedChanged event for the check button, the event args have to include a reference to an unused dummy button in addition to the actual button. And even after experimenting with various renderers, I couldn't get the checked + mouse over render to be exactly the same as that for a normal check button, so I just draw a colour overlay, which is nearly right but not quite.

Passing This(of my main form) to a class that handles direction change/and language change c#/Winforms

I am having trouble with this,
What i am trying to achieve is the following:
I have a main form which is the startup form (MDI Container)
and then a few forms that are the child forms, what i am trying to achieve is the following, this is code that is happening on every screen at the moment, and i want to build it into a class but, i don't know how to pass this (the click events happen on every form at the moment, and i want it to run of a menu click on the MDI Parent(container):
UPDATE
I have gotten it to work with all the ChildForms thanx #JamesBarras this is how my code works now, i have remove the button click events, for it is only running from a MenuStripItem_Click Event
#region Change Language
private void englishToolStripMenuItem_Click(object sender, EventArgs e) {
Class1 cls = new Class1();
/// This is for Main and all the forms of MainForms Children
foreach (var childForm in this.MdiChildren) {
cls.ChangeLanguage(sender, log, childForm, this, this.menuStrip, "en");
//ChangeLanguage("en", childForm);
}
}
private void arabicToolStripMenuItem_Click(object sender, EventArgs e) {
Class1 cls = new Class1();
/// This is for Main and all the forms of MainForms Children
foreach (var childForm in this.MdiChildren) {
cls.ChangeLanguage(sender, log, childForm, this, this.menuStrip, "ar");
//ChangeLanguage( "ar", childForm);
}
}
#endregion
I just created this class as this code was running on the same page as the click event as a Method, I want the class that i am using/created (ChangeLanguage) to know what this is,because on the normal form, i can say this but in a class it doesn't know what the this is on the form that i have my click events, here is my code in the class that i created:
UPDATE
I have fine tuned the Class to do exactly what i need it to do, yet, i still can't translate the MenuStripItems and i am getting the value of this but still no menustrip Translate only the direction switch works
namespace languageChange.Classes { class Class1 {
#region Methods
/// <summary>
/// This is mainly to change the language and the layout direction
/// </summary>
/// <param name="sender"></param>
/// <param name="log"></param>
/// <param name="form"></param>
/// <param name="thiss"></param>
/// <param name="strip"></param>
/// <param name="lang"></param>
public void ChangeLanguage(object sender, Logger log, Form form, Form thiss, Control strip, string lang) {
string senderText = sender.GetType().ToString();
RightToLeft direction = RightToLeft.No;
if (lang == "ar") {
direction = RightToLeft.Yes;
}
thiss.RightToLeft = direction;
CultureInfo CurrentLocale = new CultureInfo(lang);
Thread.CurrentThread.CurrentCulture = new CultureInfo(lang);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang);
foreach (Control childForm in form.Controls) {
ComponentResourceManager resources = new ComponentResourceManager(form.GetType());
resources.ApplyResources(childForm, "$this");
childForm.RightToLeft = direction;
if (log.isDebugEnabled) log.Debug("--------------------------------------> c = " + childForm.Name);
RefreshResources(log, lang, childForm, resources, CurrentLocale, strip);
}
}
/// <summary>
/// This is to for the Refresh all the Resources
/// </summary>
/// <param name="log"></param>
/// <param name="lang"></param>
/// <param name="ctrl"></param>
/// <param name="resources"></param>
/// <param name="CurrentLocale"></param>
/// <param name="strip"></param>
private static void RefreshResources(Logger log, string lang, Control ctrl, ComponentResourceManager resources, CultureInfo CurrentLocale, Control strip) {
ctrl.SuspendLayout();
resources.ApplyResources(ctrl, ctrl.Name, CurrentLocale);
foreach (Control control in ctrl.Controls) {
RefreshResources(log, lang, control, resources, CurrentLocale, strip); // recursion
if (strip is ToolStrip) {
RefreshResources(((ToolStrip)strip).Items, resources, CurrentLocale);
}
ctrl.ResumeLayout(true);
if (log.isTraceEnabled) log.Trace("c=" + ctrl.Name);
}
}
/// <summary>
/// Which is done here [Refer to previous Summary]
/// </summary>
/// <param name="col"></param>
/// <param name="resources"></param>
/// <param name="CurrentLocale"></param>
private static void RefreshResources(ToolStripItemCollection col, ComponentResourceManager resources, CultureInfo CurrentLocale) {
foreach (ToolStripMenuItem item in col) {
if (item is ToolStripMenuItem) {
RefreshResources(((ToolStripMenuItem)item).DropDownItems, resources, CurrentLocale);
}
resources.ApplyResources(item, item.Name, CurrentLocale);
}
}
#endregion }}
UPDATE
So i got everything to translate, and change direction except for the MenuStripItems and the Menustrip as well.
Please Help me as this is really important and i am struggling.
This is a Winforms application using Visual Studio 2013 Ultimate, with C#.
Don't mind the logger, it is something i just added to log the trace and debugger
Change the signature of ChangeLanguage(string lang) to ChangeLanguage(Form form, string lang) so that you can pass it the form you wish to alter.
subsequently you now need to refer to the form you are working on as the passed in one. So replace this in that method with form and the forms type typeof(Form1) with form.GetType()
Everywhere you want to change language on any for you can now call
Class1.ChangeLanguage(this, "en");
where this is the current form
To change language for an MDIParent you'll need to this:
private void englishToolStripMenuItem_Click(object sender, EventArgs e)
{
Class1.ChangeLanguage(log, this, "en");
foreach(var child in this.MdiChildren)
{
Class1.ChangeLanguage(log, child, "en");
}
}
Note that to change the language of the MenuStrip itself you'll need to add an overload of RefreshResources to cope with ToolStripItemCollections (they are component based not control based)
private static void RefreshResources(ToolStripItemCollection col, ComponentResourceManager resources, CultureInfo CurrentLocale)
{
foreach(ToolStripItem item in col)
{
if (item is ToolStripMenuItem)
{
RefreshResources(((ToolStripMenuItem)item).DropDownItems, resources, CurrentLocale);
}
resources.ApplyResources(item, item.Name, CurrentLocale);
}
}
and add the following code to the original RefreshResources
if (ctrl is ToolStrip)
{
RefreshResources(((ToolStrip)ctrl).Items, resources, CurrentLocale);
}

detecting textboxes anywhere on windows with wpf

I was wondering how to click on any textbox and call upon an application like on screen keyboard every time I click on them. Does WPF limit me to just the program or can I click on any textbox, like on browsers as well?
Thanks
Yes you can certainly do this - I am doing pretty much exactly what you're asking in an SDK I have written. You can define an attached dependency property that subscribes to the focus and left click events for a textbox. These handlers then trigger the onscreen keyboard to get displayed.
To apply this attached dependency property to all your textboxes you simply define a global style for text boxes. So the global style will look like the following. Note how both the key and target type have the same value. This is what makes your app apply it to all textboxes in the app. You just need to put this style in a global location in your app, e.g. the resources section of your App.xaml.
<Style x:Key="{x:Type TextBox}" TargetType="{x:Type TextBox}">
<Setter Property="sdk:ClientKeyboardController.AutoShowKeyboard" Value="True"/>
</Style>
Here is a cut down version of my attached dependency property, you need to fill in the blanks to popup your on-screen keyboard:
public class ClientKeyboardController : DependencyObject
{
/// <summary>
/// <para>
/// Set this property to true to enable the focus based keyboard popup functionality.
/// This will request the client device show it's on-screen keyboard whenever the text
/// box gets focus.
/// </para>
/// <para>
/// Note: to hide the keyboard, either enable the AutoHideKeyboard dependency property
/// as well, or manually hide the keyboard at an appropriate time.
/// </para>
/// </summary>
public static readonly DependencyProperty AutoShowKeyboardProperty = DependencyProperty.RegisterAttached(
"AutoShowKeyboard",
typeof(bool),
typeof(ClientKeyboardController),
new PropertyMetadata(false, AutoShowKeyboardPropertyChanged));
/// <summary>
/// <see cref="AutoShowKeyboardProperty"/> getter.
/// </summary>
/// <param name="obj">The dependency object to get the value from.</param>
/// <returns>Gets the value of the auto show keyboard attached property.</returns>
[AttachedPropertyBrowsableForChildrenAttribute(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
public static bool GetAutoShowKeyboard(DependencyObject obj)
{
return (bool)obj.GetValue(AutoShowKeyboardProperty);
}
/// <summary>
/// <see cref="AutoShowKeyboardProperty"/> setter.
/// </summary>
/// <param name="obj">The dependency object to set the value on.</param>
/// <param name="value">The value to set.</param>
public static void SetAutoShowKeyboard(DependencyObject obj, bool value)
{
obj.SetValue(AutoShowKeyboardProperty, value);
}
/// <summary>
/// Set this property to true to enable the focus based keyboard hide functionality.
/// This will request the client device to hide it's on-screen keyboard whenever the
/// text box loses focus.
/// </summary>
public static readonly DependencyProperty AutoHideKeyboardProperty = DependencyProperty.RegisterAttached(
"AutoHideKeyboard",
typeof(bool),
typeof(ClientKeyboardController),
new PropertyMetadata(false, AutoHideKeyboardPropertyChanged));
/// <summary>
/// AutoHideKeyboard getter
/// </summary>
/// <param name="obj">The dependency object we want the value from.</param>
/// <returns>Gets the value of the auto hide keyboard attached property.</returns>
[AttachedPropertyBrowsableForChildrenAttribute(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
public static bool GetAutoHideKeyboard(DependencyObject obj)
{
return (bool)obj.GetValue(AutoHideKeyboardProperty);
}
/// <summary>
/// AutoHideKeyboard setter.
/// </summary>
/// <param name="obj">The dependency object to set the value on.</param>
/// <param name="value">The value to set.</param>
public static void SetAutoHideKeyboard(DependencyObject obj, bool value)
{
obj.SetValue(AutoHideKeyboardProperty, value);
}
/// <summary>
/// Handler for the AutoShowKeyboard dependency property being changed.
/// </summary>
/// <param name="d">Object the property is applied to.</param>
/// <param name="e">Change args</param>
private static void AutoShowKeyboardPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBoxBase textBox = d as TextBoxBase;
if (null != textBox)
{
if ((e.NewValue as bool?).GetValueOrDefault(false))
{
textBox.GotKeyboardFocus += OnGotKeyboardFocus;
textBox.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
}
else
{
textBox.GotKeyboardFocus -= OnGotKeyboardFocus;
textBox.PreviewMouseLeftButtonDown -= OnMouseLeftButtonDown;
}
}
}
/// <summary>
/// Handler for the AutoHideKeyboard dependency property being changed.
/// </summary>
/// <param name="d">Object the property is applied to.</param>
/// <param name="e">Change args</param>
private static void AutoHideKeyboardPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBoxBase textBox = d as TextBoxBase;
if (null != textBox)
{
if ((e.NewValue as bool?).GetValueOrDefault(false))
{
textBox.LostKeyboardFocus += OnLostKeyboardFocus;
}
else
{
textBox.LostKeyboardFocus -= OnLostKeyboardFocus;
}
}
}
/// <summary>
/// Left click handler. We handle left clicks to ensure the text box gets focus, and if
/// it already has focus we reshow the keyboard. This means a user can reshow the
/// keyboard when the text box already has focus, i.e. they don't have to swap focus to
/// another control and then back to the text box.
/// </summary>
/// <param name="sender">The text box that was clicked on.</param>
/// <param name="e">The left mouse click event arguments.</param>
private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
TextBoxBase textBox = sender as TextBoxBase;
if (null != textBox)
{
if (textBox.IsKeyboardFocusWithin)
{
// TODO: Show on-screen keyboard
}
else
{
// Ensure focus is set to the text box - the focus handler will then show the
// keyboard.
textBox.Focus();
e.Handled = true;
}
}
}
/// <summary>
/// Got focus handler. Displays the client keyboard.
/// </summary>
/// <param name="sender">The text box that received keyboard focus.</param>
/// <param name="e">The got focus event arguments.</param>
private static void OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
TextBoxBase textBox = e.OriginalSource as TextBoxBase;
if (textBox != null)
{
// TODO: Show on-screen keyboard
}
}
/// <summary>
/// Lost focus handler. Hides the client keyboard. However we skip hiding if the new
/// element that gains focus has got the auto-show keyboard attached property set to
/// true. This prevents screen glitching from quickly showing/hiding the keyboard.
/// </summary>
/// <param name="sender">The text box that lost keyboard focus.</param>
/// <param name="e">The lost focus event arguments.</param>
private static void OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
TextBoxBase textBox = e.OriginalSource as TextBoxBase;
if (textBox != null)
{
bool skipHide = false;
if (e.NewFocus is DependencyObject)
{
skipHide = GetAutoShowKeyboard(e.NewFocus as DependencyObject);
}
if (!skipHide && ClientKeyboardController.CmpInput != null)
{
// TODO: Hide on-screen keyboard.
}
}
}
}
Note that this approach only works for your current WPF application. You will need to look at hooking mechanisms if you want to do this for other processes. E.g. You could use the Microsoft Active Accessibility APIs or a hooking library like EasyHook.
You will probably need to use something like hooks to get to textboxes etc in other applications. Have a look at something like in this link global hooks

OnPaint not getting called if form doesn't have focus

I have a user control with custom painting. Constructor sets styles correctly, from what I can tell. Basic code:
public partial class LineChart2 : UserControl
{
public LineChart2()
{
InitializeComponent();
//Set control styles to eliminate flicker on redraw and to redraw on resize
this.SetStyle(
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.DoubleBuffer,
true);
SetDefaultValues();
}
protected override void OnPaint(PaintEventArgs e)
{
// breakpoint set here for verification
Paint~misc stuff(e.Graphics);
base.OnPaint(e);
}
private void UpdateGraph()
{
// this is called when the data that the control depends on changes
~update stuff();
this.Invalidate();
//this.Refresh();
}
}
The control is contained within a Panel on a standard WinForm.
I've tried both Invalidate and Refresh.
When using Invalidate(), the control will redraw properly as long as the form it is contained in has focus. Drawing is smooth. When I switch focus to another form, drawing ceases even though the events are still firing, and this.Invalidate() is still being called. The form is still fully visible on screen.
When using Refresh(), the control will redraw regardless of whether the form has focus, but the drawing constantly flickers, as if bypassing the double-buffering mechanism.
So how do I get the Invalidate message to properly invoke the OnPaint method regardless of focus?
Documentation says:
Calling the Invalidate method does not
force a synchronous paint; to force a
synchronous paint, call the Update
method after calling the Invalidate
method.
Have you tried calling Update after Invalidate?
You should not force the control do redraw (Update or Refresh) so often. The UI may get not responsive, others controls may not update, because you are giving all UI attention to the forced sync Refresh.
The right way is to draw only when UI is ready to do it. For that you need a render loop. The ApplicationLoopDoWork will be fired every time the UI is ready to draw something. The period depends on the machine speed and what is being redrawn.
The class is based on this post on Tom Miller's Blog.
Here is the class that I use to control that.
Make updates only on the ApplicationLoopDoWork call.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace Utilities.UI
{
/// <summary>
/// WinFormsAppIdleHandler implements a WinForms Render Loop (max FPS possible).
/// Reference: http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
/// </summary>
public sealed class WinFormsAppIdleHandler
{
private readonly object _completedEventLock = new object();
private event EventHandler _applicationLoopDoWork;
//PRIVATE Constructor
private WinFormsAppIdleHandler()
{
Enabled = false;
SleepTime = 10;
}
/// <summary>
/// Singleton from:
/// http://csharpindepth.com/Articles/General/Singleton.aspx
/// </summary>
private static readonly Lazy<WinFormsAppIdleHandler> lazy = new Lazy<WinFormsAppIdleHandler>(() => new WinFormsAppIdleHandler());
public static WinFormsAppIdleHandler Instance { get { return lazy.Value; } }
private bool _enabled = false;
/// <summary>
/// Gets or sets if must fire ApplicationLoopDoWork event.
/// </summary>
public bool Enabled
{
get { return _enabled; }
set {
if (value)
Application.Idle += Application_Idle;
else
Application.Idle -= Application_Idle;
_enabled = value;
}
}
/// <summary>
/// Gets or sets the minimum time betwen ApplicationLoopDoWork fires.
/// </summary>
public int SleepTime { get; set; }
/// <summary>
/// Fires while the UI is free to work. Sleeps for "SleepTime" ms.
/// </summary>
public event EventHandler ApplicationLoopDoWork
{
//Reason of using locks:
//http://stackoverflow.com/questions/1037811/c-thread-safe-events
add
{
lock (_completedEventLock)
_applicationLoopDoWork += value;
}
remove
{
lock (_completedEventLock)
_applicationLoopDoWork -= value;
}
}
/// <summary>
///Application idle loop.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Application_Idle(object sender, EventArgs e)
{
//Try to update interface
while (Enabled && IsAppIdle())
{
OnApplicationIdleDoWork(EventArgs.Empty);
//Give a break to the processor... :)
//8 ms -> 125 Hz
//10 ms -> 100 Hz
Thread.Sleep(SleepTime);
}
}
private void OnApplicationIdleDoWork(EventArgs e)
{
var handler = _applicationLoopDoWork;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// Gets if the app is idle.
/// </summary>
/// <returns></returns>
public static bool IsAppIdle()
{
bool isIdle = false;
try
{
Message msg;
isIdle = !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
}
catch (Exception e)
{
//Should never get here... I hope...
MessageBox.Show("IsAppStillIdle() Exception. Message: " + e.Message);
}
return isIdle;
}
#region Unmanaged Get PeekMessage
// http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
#endregion
}
}
You also might try Invalidate(true) to trigger child controls to repaint as well.

Categories