OnPaint not getting called if form doesn't have focus - c#

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.

Related

Custom control: Adding custom properties from another class [duplicate]

Im trying to create a custom control that inherits NumericUpDown to show a settable unit.
This is (visually) what I've got so far:
My Code: Looks a bit long, but isnt doing that much
class NumericUpDownUnit : NumericUpDown
{
public event EventHandler ValueChanged;
/// <summary>
/// Constructor creates a label
/// </summary>
public NumericUpDownUnit()
{
this.TextChanged += new EventHandler(TextChanged_Base);
this.Maximum = 100000000000000000;
this.DecimalPlaces = 5;
this.Controls.Add(lblUnit);
lblUnit.BringToFront();
UpdateUnit();
}
public void TextChanged_Base(object sender, EventArgs e)
{
if(ValueChanged != null)
{
this.ValueChanged(sender, e);
}
}
/// <summary>
/// My designer property
/// </summary>
private Label lblUnit = new Label();
[Description("The text to show as the unit.")]
public string Unit
{
get
{
return this.lblUnit.Text;
}
set
{
this.lblUnit.Text = value;
UpdateUnit();
}
}
/// <summary>
/// When unit has changed, calculate new label-size
/// </summary>
public void UpdateUnit()
{
System.Drawing.Size size = TextRenderer.MeasureText(lblUnit.Text, lblUnit.Font);
lblUnit.Padding = new Padding(0, 0, 0, 3);
lblUnit.Size = new System.Drawing.Size(size.Width, this.Height);
lblUnit.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
lblUnit.BackColor = System.Drawing.Color.Transparent;
lblUnit.Location = new System.Drawing.Point(this.Width-lblUnit.Width-17, 0);
}
/// <summary>
/// If text ends with seperator, skip updating text as it would parse without decimal palces
/// </summary>
protected override void UpdateEditText()
{
if (!this.Text.EndsWith(".") && !this.Text.EndsWith(","))
Text = Value.ToString("0." + new string('#', DecimalPlaces));
}
/// <summary>
/// Culture fix
/// </summary>
protected override void OnKeyPress(KeyPressEventArgs e)
{
if (e.KeyChar.Equals('.') || e.KeyChar.Equals(','))
{
e.KeyChar = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator.ToCharArray()[0];
}
base.OnKeyPress(e);
}
/// <summary>
/// When size changes, call UpdateUnit() to recalculate the lable-size
/// </summary>
protected override void OnResize(EventArgs e)
{
UpdateUnit();
base.OnResize(e);
}
/// <summary>
/// Usability | On enter select everything
/// </summary>
protected override void OnEnter(EventArgs e)
{
this.Select(0, this.Text.Length);
base.OnMouseEnter(e);
}
/// <summary>
/// If, when leaving, text ends with a seperator, cut it out
/// </summary>
protected override void OnLeave(EventArgs e)
{
if(this.Text.EndsWith(System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator))
{
this.Text = this.Text.Substring(0, this.Text.Length - 1);
}
base.OnLeave(e);
}
}
My problem:
The lable is currently covering the end of the box. So if a big value comes in (or the size is low) it gets covered by the label as seen in here:
I know that the NumericUpDown has something like a scroll-function when a typed in value is longer than the size of the inputbox. This is triggered at the end of the box.
Is there in any way the possibility of setting up something like padding for the text inside the box? For example setting the padding on the right to the size of my label?
I like this custom control pretty much but this one last thing is annoying.
Unfortunately I dont know how to lookup the properties of an existing control as for example there is a method called UpdateEditText(). Maybe someone can tell me how to lookup this base functions/properties.
Thanks a lot!
NumericUpDown is a control which inherits from UpDownBase composite control. It contains an UpDownEdit and an UpDownButtons control. The UpDownEdit is a TextBox. You can change appearance of the control and its children. For example, you can add a Label to the textbox control and dock it to the right of TextBox, then set text margins of textbox by sending an EM_SETMARGINS message to get such result:
Code
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class ExNumericUpDown : NumericUpDown
{
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hwnd, int msg, int wParam, int lParam);
private const int EM_SETMARGINS = 0xd3;
private const int EC_RIGHTMARGIN = 2;
private Label label;
public ExNumericUpDown() : base()
{
var textBox = Controls[1];
label = new Label() { Text = "MHz", Dock = DockStyle.Right, AutoSize = true };
textBox.Controls.Add(label);
}
public string Label
{
get { return label.Text; }
set { label.Text = value; if (IsHandleCreated) SetMargin(); }
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
SetMargin();
}
private void SetMargin()
{
SendMessage(Controls[1].Handle, EM_SETMARGINS, EC_RIGHTMARGIN, label.Width << 16);
}
}

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.

Designing a "busy" dialog using InteractionRequests

I'm currently using Prism's InteractionRequest to display new windows. I use them for simple confirmations as well as displaying a window window with a custom view/viewmodel, following the sample here. Anyway, in all of these cases, I display the window and some button on the window is responsible for closing it. I'd like to display a window and have the object that called it be responsible for closing it.
Here is my implementation:
ActionNotification
public abstract class ActionNotification: Notification, INotifyPropertyChanged, IPopupWindowActionAware
{
public event PropertyChangedEventHandler PropertyChanged;
// IPopupWindowActionAware
public System.Windows.Window HostWindow { get; set; } // Set when the "action" in the view is triggered
public Notification HostNotification { get; set; } // Set when the "action" in the view is triggered
public ActionNotification(string content)
{
this.Content = content;
}
public void CompleteAction()
{
if (this.HostWindow != null)
{
this.HostWindow.Close();
}
}
// INotifyPropertyChange implementation
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Calling method
/// <summary>
/// Pushes a unit of work onto a separate thread and notifies the view to display an action notification
/// </summary>
/// <param name="actionNotification">The notification object for the view to display</param>
/// <param name="act">The unit of work to perform on a separate thread</param>
private void DoWorkAndRaiseAction(ActionNotification actionNotification, Action act)
{
Task.Factory.StartNew(() =>
{
try
{
act();
}
finally
{
Application.Current.Dispatcher.Invoke((Action)(() => actionNotification.CompleteAction()));
}
});
ActionInteractionReq.Raise(actionNotification);
}
This all works well but it appears that I would be suck if the "work" completed before I was able to raise the InteractionRequest. Can anyone offer some advice to GUARANTEE either the work hasn't completed before raising the request otherwise don't raid the request?
EDIT: I should add that the window is being shown as modal, so no code is executed after the request is raised, which is why I push the work off onto a separate task
EDIT2: Here is how the view interacts with the request:
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding Path=ActionInteractionReq, Mode=OneWay}">
<int_req:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True" WindowStyle="None" WindowHeight="150" WindowWidth="520">
<int_req:PopupWindowAction.WindowContent>
<int_req:ZActionNotificationView/>
</int_req:PopupWindowAction.WindowContent>
</int_req:PopupWindowAction>
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
When Raise is called, the PopupWindowAction is triggered and creates a new Window. It then does ShowDialog on that window.
EDIT3: From the advice from the comments, I've included the PopupWindowAction. I've cut out some irrelevant code for the sake of brevity
public class PopupWindowAction : TriggerAction<FrameworkElement>
{
/*
Here is where a few dependency properties live that dictate things like Window size and other stuff, e.g.
/// <summary>
/// Determines if the content should be shown in a modal window or not.
/// </summary>
public static readonly DependencyProperty IsModalProperty =
DependencyProperty.Register(
"IsModal",
typeof(bool),
typeof(PopupWindowAction),
new PropertyMetadata(null));
*/
/*
Here is where the accessors live for the DPs, e.g.
/// <summary>
/// Gets or sets if the window will be modal or not.
/// </summary>
public bool IsModal
{
get { return (bool)GetValue(IsModalProperty); }
set { SetValue(IsModalProperty, value); }
}
*/
#region PopupWindowAction logic
/// <summary>
/// Displays the child window and collects results for <see cref="IInteractionRequest"/>.
/// </summary>
/// <param name="parameter">The parameter to the action. If the action does not require a parameter, the parameter may be set to a null reference.</param>
protected override void Invoke(object parameter)
{
var args = parameter as InteractionRequestedEventArgs;
if (args == null)
{
return;
}
// If the WindowContent shouldn't be part of another visual tree.
if (this.WindowContent != null && this.WindowContent.Parent != null)
{
return;
}
var wrapperWindow = this.GetWindow(args.Context); // args.Context here is the Notification object I'm passing to the InteractionRequest
var callback = args.Callback;
EventHandler handler = null;
handler =
(o, e) =>
{
wrapperWindow.Closed -= handler;
wrapperWindow.Owner = null;
wrapperWindow.Content = null;
callback();
};
wrapperWindow.Closed += handler;
if (this.IsModal)
{
wrapperWindow.ShowDialog();
}
else
{
wrapperWindow.Show();
}
}
/// <summary>
/// Checks if the WindowContent or its DataContext implements IPopupWindowActionAware and IRegionManagerAware.
/// If so, it sets the corresponding values.
/// Also, if WindowContent does not have a RegionManager attached, it creates a new scoped RegionManager for it.
/// </summary>
/// <param name="notification">The notification to be set as a DataContext in the HostWindow.</param>
/// <param name="wrapperWindow">The HostWindow</param>
protected void PrepareContentForWindow(Notification notification, Window wrapperWindow)
{
if (this.WindowContent == null)
{
return;
}
// We set the WindowContent as the content of the window.
wrapperWindow.Content = this.WindowContent;
/* Code removed for brevity */
// If the WindowContent implements IPopupWindowActionAware, we set the corresponding values.
IPopupWindowActionAware popupAwareContent = this.WindowContent as IPopupWindowActionAware;
if (popupAwareContent != null)
{
popupAwareContent.HostWindow = wrapperWindow;
popupAwareContent.HostNotification = notification;
}
// If the WindowContent's DataContext implements IPopupWindowActionAware, we set the corresponding values.
IPopupWindowActionAware popupAwareDataContext = this.WindowContent.DataContext as IPopupWindowActionAware;
if (popupAwareDataContext != null)
{
popupAwareDataContext.HostWindow = wrapperWindow;
popupAwareDataContext.HostNotification = notification;
}
}
#endregion
#region Window creation methods
/// <summary>
/// Returns the window to display as part of the trigger action.
/// </summary>
/// <param name="notification">The notification to be set as a DataContext in the window.</param>
/// <returns></returns>
protected Window GetWindow(Notification notification)
{
Window wrapperWindow;
if (this.WindowContent != null)
{
wrapperWindow = new Window();
wrapperWindow.WindowStyle = this.WindowStyle;
// If the WindowContent does not have its own DataContext, it will inherit this one.
wrapperWindow.DataContext = notification;
wrapperWindow.Title = notification.Title ?? string.Empty;
this.PrepareContentForWindow(notification, wrapperWindow);
}
else
{
wrapperWindow = this.CreateDefaultWindow(notification);
wrapperWindow.DataContext = notification;
}
return wrapperWindow;
}
private Window CreateDefaultWindow(Notification notification)
{
return new DefaultNotificationWindow
{
NotificationTemplate = this.ContentTemplate,
MessageBoxImage = GetImageFromNotification(notification as ZBaseNotification)
};
}
#endregion
}
The underlying issue here is that the code that starts the async operation and the code that displays the window are just not cooperating. The design based on IPopupWindowActionAware is IMHO not very good; pushing property values around is OK for common scenarios, but here it starts showing its limitations.
Let's first consider a localized solution that works with the current code:
public Window HostWindow { /* call OnPropertyChanged! */ }
public void CompleteAction()
{
if (this.HostWindow != null)
{
this.HostWindow.Close();
}
else
{
this.PropertyChanged += (o, e) => {
if (e.PropertyName == "HostWindow" && this.HostWindow != null)
{
var hostWindow = this.HostWindow; // prevent closure-related bugs
// kill it whenever it appears in the future
hostWindow.Loaded += (o, e) => { hostWindow.Close(); };
// kill it right now as well if it's been shown already
// (we cannot assume anything)
if (hostWindow.IsLoaded)
{
hostWindow.Close();
}
}
};
}
}
This is not quite elegant, but it does the job: if CompleteAction is called before the window is known, then when the window becomes known we attach a handler that closes it immediately whenever it get displayed. The double-deep event handler assignment is necessary because the window might not be shown at the time it becomes known to us.

implementing a progress bar to show work being done

I have one form Form1, and it contains two elements button and progress bar.
When I click on button it calls class.SomeFunction(), which then calls a few other functions from different classes to complete some work.
I would like to increase value of progress bar from inside class.SomeFunctin() and all other functions that SomeFunctino call
class #class
{
public static void SomeFunction(var something)
{
progressbar.value++;
class2.Function2(something);
}
}
class class2
{
public static void Function2(var something)
{
progressbar.value++;
}
}
How can this be done?
You really shouldn't have those functions update the progressbar-- it violates the single responsibility principle. You are better off using a backgroundworker or just update the progressbar from within your button_click event after each function call.
If you are doing something that takes so long you have to show a progress bar, then you should be doing it in a background thread and not the form. That will make the UI become unresponsive.
A Code Project BackgroundWorker Thread article has an example of a background thread that shows a progress bar in a WinForms app.
The easiest way for you to do this is simply to call an event that is handled in your form, and in the event handler have that increment the progress bar.
What you will first want to do is create a custom EventArgs.
public class ProgressEventArgs : EventArgs
{
public int Progress {get; set;}
public ProgressEventArgs(int progress)
{
Progress = progress;
}
}
Then in your classes that you want to increment the Progress bar you will want to raise this event.
class Class2
{
public event EventHandler<ProgressEventArgs> ProgressEvent;
public void Function2(var something)
{
OnRaiseProgressEvent(new ProgressEventArgs(1));
}
protected virtual void OnRaiseProgressEvent(ProgressEventArgs e)
{
// C# 6 and above:
// Raise event if event handler is set (i.e. not null)
ProgressEvent?.Invoke(this, e);
// end C# >=6 code
// C# 5 and earlier:
EventHandler<ProgressEventArgs> handler = ProgressEvent;
if(handler != null)
{
//this is what actually raises the event.
handler(this, e);
}
// end C# <=5 code
}
}
Then in your form you will want to subscribe to the event
public class YourForm
{
public YourForm
{
Class2 yourClass2Instance = new Class2();
yourClass2Instance.ProgressEvent += ProgressEventHandler;
}
private void ProgressEventHandler(object sender, ProgressEventArgs e)
{
progressbar.Value += e.Progress;
}
}
In the past I've used a simple menu bar-less form with a label and a Forms.ProgressBar on it using the following code in the form:
public partial class ProgressDialog : Form
{
//public delegate delSetProgress
private readonly int progressBarMax;
/// <summary>
/// Structure used for passing progress bar related parameters as a single variable.
/// </summary>
public struct ProgressBarParams
{
public int value;
public string message;
public ProgressBarParams(string Message, int Value)
{
message = Message;
value = Value;
}
}
/// <summary>
/// Constructs the progress bar dialog and sets the progress bar's maximum value to maxValue.
/// </summary>
/// <param name="maxValue">Value to set to progress bar's Maximum property.</param>
public ProgressDialog(int maxValue)
{
InitializeComponent();
progressBarMax = maxValue;
}
private void ProgressDialog_Load(object sender, EventArgs e)
{
progressBar.Maximum = progressBarMax;
}
/// <summary>
/// Public method to update the progressDialog
/// </summary>
/// <param name="inputParams">Values to update on the progressDialog</param>
public void SetProgress(ProgressBarParams inputParams)
{
lblMessage.Text = inputParams.message;
progressBar.setValue(inputParams.value);
Update();
}
/// <summary>
/// This method should be called when the operation represented by the ProgressDialog is
/// completed. It shows an "operation complete" message for a second and then closes the form.
/// </summary>
public void Finish()
{
lblMessage.Text = "Operation complete.";
progressBar.setValue(progressBar.Maximum);
Update();
System.Threading.Thread.Sleep(1000);
this.Close();
}
}
public static class MyExtensions
{
/// <summary>
/// Implements a hack to get around a stupid rendering problem with the .NET progress bar in some environments.
/// Sets the progress bar value property.
/// </summary>
/// <param name="proBar">Progress bar control to set the value on.</param>
/// <param name="value">Value to be set.</param>
public static void setValue(this ProgressBar proBar, int value)
{
if (value > 0)
{
proBar.Value = value;
proBar.Value = value - 1;
proBar.Value = value;
}
else
{
proBar.Value = value;
proBar.Value = value + 1;
proBar.Value = value;
}
}
}
Note the setValue extension method that uses a workaround to avoid an issue with some versions of Windows.
I then set it (and a splash screen) up with the following, where m_frmProgress is the progress bar form:
// Create splash screen/progress bar thread on the thread pool
ThreadPool.QueueUserWorkItem((x) =>
{
bool localDone = false;
m_frmSplash.Show();
m_frmProgress.Show();
// Set the progress bar form up slightly below the bottom of the splash screen
m_frmProgress.Location = new Point(m_frmProgress.Location.X, m_frmProgress.Location.Y + (m_frmSplash.Height / 2) + (m_frmProgress.Height / 2) + 10);
while (!localDone) // Keep iterating until the main thread tells us we're done
{
lock (splashScreenDoneLock)
{
localDone = splashScreenDone;
}
// I don't like this method of keeping the UI responsive, but as yet I have no better method
Application.DoEvents();
Thread.Sleep(500);
}
m_frmSplash.Close();
m_frmProgress.Close();
while (!m_frmProgress.IsDisposed || !m_frmSplash.IsDisposed) // While either splash form is not disposed (still active)
{
Thread.Sleep(100); // Keep waiting until the splash forms are gone
}
splashFormsDisposed.Set(); // Signal the main thread that the splash forms are gone so the main form can be shown
});
bool isSplashHandleCreated = false;
bool isProgressHandleCreated = false;
// Wait until both splash forms are created
while (!(isSplashHandleCreated && isProgressHandleCreated))
{
lock (m_frmSplash)
{
isSplashHandleCreated = m_frmSplash.IsHandleCreated;
}
lock (m_frmProgress)
{
isProgressHandleCreated = m_frmProgress.IsHandleCreated;
}
Thread.Sleep(500);
}
And invoke it like this:
m_frmProgress.Invoke(new Action<ProgressDialog.ProgressBarParams>(m_frmProgress.SetProgress), progressLevel);
It's not the most elegant method, but it gives you a cleanly update-able progress bar on a separate thread that will stay responsive while you're messing with it. I pretty much copy and pasted all that code from a working app, so it should work. On the flip side, I apologize if any of it is unclear.

OnMouseEnter for all controls on a form

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 ...

Categories