implementing a progress bar to show work being done - c#

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.

Related

How to make a simple imgui popup window in C#

Im trying to make a simple popup window where i can input text but i am having a hard time getting started.
internal sealed class UpdatelogWindow : Window, IDisposable
{
public UpdatelogWindow()
: base("What's new?", ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoResize)
{
this.Namespace = "Update Window";
this.Size = new Vector2(885, 463);
this.SizeCondition = ImGuiCond.Appearing;
}
public override void Draw()
{
ImGui.Text($"Test.");
}
/// <summary>
/// Dispose this window.
/// </summary>
public void Dispose()
{
this.Dispose();
}
}
This is what i have at the moment but nothing is showing up

How to trigger an event when something is clicked 5 times?

I know this is probably a stupid question with an easy answer, but I can't figure it out. I'm trying to trigger an event when a label is pressed 5 times. How could I go about doing this?
First of all, we don't know what label do you use - Windows Forms, Web Forms, WPF? For example, if you use Web Forms, this is completely different story with postbacks etc. WPF is also completely different "animal".
So, if this is a windows forms label or other control, just declare a class-variable/member and do something when it clicked 5 times and then reset it
public class Clickable
{
private int _counter = 0;
private void SomeControl_Click(object sender, EventArgs e)
{
_counter++;
if (_counter == 5)
{
// DO SOMETHING HERE
MySpecialMethod();
// And then reset counter so you can click 5 times again
_counter = 0;
}
}
}
I know this has been resolved, but I wanted to do the same thing recently and I'd like to share my implementation.
What I wanted was: do something when the user clicks 5 times within 2 seconds
Here's my helper class ClickStreakMachine.cs
using System;
namespace MyApp01;
/// <summary>
/// A helper class that invokes an action when the Click method is triggered enough times within a certain time window
/// </summary>
public class ClickStreakMachine
{
private readonly int _requiredClicks;
private readonly TimeSpan _maxClickSpacing;
private readonly Action _action;
private DateTime _lastClickedAt = DateTime.MinValue;
private int _clickStreak = 0;
/// <summary>
/// A helper class that invokes an action when the Click method is triggered enough times within a certain time window
/// </summary>
/// <param name="requiredClicks">Clicks required to trigger the action</param>
/// <param name="maxClickSpacing">Max distance between clicks</param>
/// <param name="action">Action to trigger when conditions are met</param>
public ClickStreakMachine(int requiredClicks, TimeSpan maxClickSpacing, Action action)
{
_requiredClicks = requiredClicks;
_maxClickSpacing = maxClickSpacing;
_action = action;
}
public void Click()
{
var now = DateTime.Now;
if (_clickStreak == 0)
{
//first click
_clickStreak++;
_lastClickedAt = now;
return;
}
//reset if clicked too late
if (now - _lastClickedAt > _maxClickSpacing)
{
_clickStreak = 1;
_lastClickedAt = now;
return;
}
//add
_clickStreak++;
_lastClickedAt = now;
//invoke action and reset streak if enough clicks
if (_clickStreak >= _requiredClicks)
{
_action?.Invoke();
_clickStreak = 0;
}
}
}
I'm using it like this:
using System.Windows.Controls;
namespace MyApp01;
public partial class MainWindow : Window
{
public MainWindow()
{
//setup the click streak machine so the user has to click 5 times (each click within 400ms of the last one) to trigger the DoSomething method
var clickStreakMachine = new ClickStreakMachine(5, TimeSpan.FromMilliseconds(400), () => DoSomething());
var label = new Label();
label.MouseLeftButtonDown += clickStreakMachine.Click();
}
}

Progressbar progress during long task

It's my first post here, so I hope I'm doing everything correct.
I'm using the .NET Framework 4 Client Profile.
I want to load data from a .doc file into my program and work with this information. This can take a lot of time since I need to run through the tables of the document and check what's inside. That is already working, the only problem here is the screen is freezing and you can't see if something is happening.
Also I know this would be faster and way easier in excel, but since this type of data is and was always stored in word-documents in our company I have to keep it like that.
So what I want to do is count all rows from the tables that I have to read, set this as my Maximum Value for the Progress-Bar and then after each row I would count the value + 1.
I have my load Button with the Command bound to LoadWordDocCmd and the progress bar:
<Button Name="btnLoadFile"
Content="Load" Height="23"
Command="{Binding LoadWordDocCmd}"
HorizontalAlignment="Right" Margin="0,22,129,0"
VerticalAlignment="Top" Width="50"
Visibility="{Binding VisModeAddNew}"
/>
<ProgressBar HorizontalAlignment="Left" Height="24" Margin="574,52,0,0"
VerticalAlignment="Top" Width="306"
Name="prgBarAddNewLoadWord"
Minimum="0"
Maximum="{Binding AddNewProgressBarMaxVal, Mode=OneWay}"
Value="{Binding AddNewProgressBarValue, Mode=OneWay}"
Visibility="{Binding AddNewProgressBarVisible}"/>
Here is the RelayCommand:
/// <summary>
/// Relaycommand for Function loadWordDocument
/// </summary>
public RelayCommand LoadWordDocCmd
{
get
{
if (this.m_loadWordDocCmd == null)
{
this.m_loadWordDocCmd = new RelayCommand(this.loadWordDocument, canLoadWordDoc);
}
return m_loadWordDocCmd;
}
private set
{
this.m_loadWordDocCmd = value;
}
}
/// <summary>
/// checks if the Word Document can be loaded
/// </summary>
/// <param name="parameter">not used</param>
/// <returns>if it could Execute, then true, else false</returns>
private bool canLoadWordDoc(object parameter)
{
bool ret = false;
if (this.m_fileSelected)
{
ret = true;
}
return ret;
}
What I already did was to work with a BackgroundWorker.
I was able to bind the Button-Command to a function that has a RelayCommand with the BackgroundWorker, but then I wasn't able to check the canExecute function anymore.
I used this to test the Progress-Bar, that was working :
xaml:
<Button ...
Command="{Binding Path=InstigateWorkCommand}"
/>
cs :
private BackgroundWorker worker;
private ICommand instigateWorkCommand;
public ProggressbarSampleViewModel()
{
this.instigateWorkCommand = new
RelayCommand(o => this.worker.RunWorkerAsync(), o => !this.worker.IsBusy);
this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
this.worker.ProgressChanged += this.ProgressChanged;
}
public ICommand InstigateWorkCommand
{
get { return this.instigateWorkCommand; }
}
private int _currentProgress;
public int CurrentProgress
{
get { return this._currentProgress; }
private set
{
if (this._currentProgress != value)
{
this._currentProgress = value;
OnPropertyChanged("CurrentProgress");
}
}
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.CurrentProgress = e.ProgressPercentage;
}
private void DoWork(object sender, DoWorkEventArgs e)
{
// do time-consuming work here, calling ReportProgress as and when you can
for (int i = 0; i < 100; i++)
{
Thread.Sleep(1000);
_currentProgress = i;
OnPropertyChanged("CurrentProgress");
}
}
But how can I get this to work with the canExecute ? Here is my function-Header:
/// <summary>
/// Function for Load Word Document
/// </summary>
/// <param name="parameter">not used</param>
private void loadWordDocument(object parameter)
Here is the Relay-Command Class:
public class RelayCommand : ICommand
{
private readonly Action<object> methodToExecute;
private readonly Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
EventHandler handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
public RelayCommand(Action<object> execute)
: this(execute, null) { }
public RelayCommand(Action<object> methodToExecute, Func<object, bool> canExecute)
{
this.methodToExecute = methodToExecute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
// wird keine canExecute-Funktion übergeben, so liefert diese
// true zurück, ansonsten wird die custom canExecute-Funktion
// mit den übergebenen Parametern aufgerufen.
return canExecute == null ? true : canExecute.Invoke(parameter);
}
public void Execute(object parameter)
{
methodToExecute(parameter);
}
}
Thank you for your help and I hope I posted this question correct!
I hope I understand your issue correctly.
The basic rule for a GUI application is: don't use the GUI thread for (time-consuming) data processing. You have to perform this task on a background thread.
Since you're using .NET 4.0 Client Profile, the async/await feature is not available to you. That would be the easiest solution, however.
You can do this with a ThreadPool instead. The BackgroundWorker is not recommended anymore.
In your XAML, you're binding the ProgressBar.Value property to a AddNewProgressBarValue property, so I assume you have a view-model with that property already. You have to ensure that changing AddNewProgressBarValue will raise the PropertyChanged event. And the good news is, the WPF Binding Engine automatically marshals the property value transfer operation to the GUI thread, so you don't need to care about which thread is changing a property your progress bar is bound to.
So the solution might look like this (not a production code, just an idea!):
class ViewModel : INotifyPropertyChanged
{
private bool isProcessing;
public bool AddNewProgressBarVisible
{
get { return this.isProcessing; }
// SetProperty here is a PRISM-like helper to set the backing field value
// and to raise the PropertyChanged event when needed.
// You might be using something similar.
private set { this.SetProperty(ref this.isProcessing, value, "AddNewProgressBarVisible");
}
private int progressValue;
public int AddNewProgressBarValue
{
get { return this.progressValue; }
private set { this.SetProperty(ref this.progressValue, value, "AddNewProgressBarValue");
}
// This is your command handler
private void LoadWordDocument(object parameter)
{
if (this.isProcessing)
{
// don't allow multiple operations at the same time
return;
}
// indicate that we're staring an operation:
// AddNewProgressBarVisible will set isProcessing = true
this.AddNewProgressBarVisible = true;
this.AddNewProgressBarValue = 0;
// Notify the bound button, that it has to re-evaluate its state.
// Effectively, this disables the button.
this.LoadWordDocCmd.RaiseCanExecuteChanged();
// Run the processing on a background thread.
ThreadPool.QueueUserWorkItem(this.DoLoadWordDocument);
}
private void DoLoadWordDocument(object state)
{
// Do your document loading here,
// this method will run on a background thread.
// ...
// You can update the progress bar value directly:
this.AddNewProgressBarValue = 42; // ...estimate the value first
// When you're done, don't forget to enable the button.
this.AddNewProgressBarVisible = false;
// We have to marshal this to the GUI thread since your ICommand
// implementation doesn't do this automatically
Application.Current.Dispatcher.Invoke(() => this.LoadWordDocCmd.RaiseCanExecuteChanged());
}
// this is your command enabler method
private bool CanLoadWordDoc(object parameter)
{
// if we're already loading a document, the command should be disabled
return this.m_fileSelected && !this.isProcessing;
}
}
I think that your ProggressbarSampleViewModel code sample is ok. I tested it and it works.
I am assuming that you want to change LoadWordDocCmd to have the behavior of InstigateWorkCommand. If you put the code from ProgressbarSampleViewModel into your actual ViewModel, you should have no problem accessing loadWordDocument and canLoadWordDoc. In addition, as mm8 mentioned, in your DoWork method you need to call RaiseCanExecuteChanged or else WPF will not check the CanExecute method.
Your ViewModel should look like bellow. See comments in upper case.
private BackgroundWorker worker;
private RelayCommand instigateWorkCommand; //CHANGE HERE
bool isBusy = false; // ADD THIS
public ProggressbarSampleViewModel()
{
//CHANGE NEXT LINE
this.instigateWorkCommand = new RelayCommand(
o => this.worker.RunWorkerAsync(),
o => !isBusy && canLoadWordDoc(null));
this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
//REMOVE
//this.worker.ProgressChanged += this.ProgressChanged;
}
public ICommand InstigateWorkCommand
{
get { return this.instigateWorkCommand; }
}
private int _currentProgress;
public int CurrentProgress
{
get { return this._currentProgress; }
private set
{
if (this._currentProgress != value)
{
this._currentProgress = value;
OnPropertyChanged("CurrentProgress");
}
}
}
//REMOVE
//private void ProgressChanged(object sender, ProgressChangedEventArgs e)
//{
// this.CurrentProgress = e.ProgressPercentage;
//}
private void DoWork(object sender, DoWorkEventArgs e)
{
//ADD NEXT LINES
isBusy = true;
Application.Current.Dispatcher.BeginInvoke(
(Action)instigateWorkCommand.RaiseCanExecuteChanged);
// do time-consuming work here, calling ReportProgress as and when you can
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(10);
_currentProgress = i;
OnPropertyChanged("CurrentProgress");
}
//ADD NEXT LINES
isBusy = false;
Application.Current.Dispatcher.BeginInvoke(
(Action)instigateWorkCommand.RaiseCanExecuteChanged);
}
bool m_fileSelected = true; //CHANGE TO SEE THE EFFECT
//REMOVE
//RelayCommand m_loadWordDocCmd;
///// <summary>
///// Relaycommand for Function loadWordDocument
///// </summary>
//public RelayCommand LoadWordDocCmd
//{
// get
// {
// if (this.m_loadWordDocCmd == null)
// {
// this.m_loadWordDocCmd = new RelayCommand(this.loadWordDocument, canLoadWordDoc);
// }
// return m_loadWordDocCmd;
// }
// private set
// {
// this.m_loadWordDocCmd = value;
// }
//}
/// <summary>
/// checks if the Word Document can be loaded
/// </summary>
/// <param name="parameter">not used</param>
/// <returns>if it could Execute, then true, else false</returns>
private bool canLoadWordDoc(object parameter)
{
bool ret = false;
if (this.m_fileSelected)
{
ret = true;
}
return ret;
}
/// <summary>
/// Function for Load Word Document
/// </summary>
/// <param name="parameter">not used</param>
private void loadWordDocument(object parameter)
{
}
Hope this helps.

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.

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