Progress bar does not update on separate form from VSTO addin - c#

On button click in Word VSTO addin, I want to show the form with progress bar and update its value.
Even though I used BackgroundWorker and its events (DoWork, ProgressChanged), progress of the progress bar does not update accordingly
private void extractDataButton_Click(object sender, RibbonControlEventArgs e)
{
//On button click of addin
ProgressNotifier progressNotifier = new ProgressNotifier();
progressNotifier.Show();
progressNotifier.UpdateProgressBar(10);
// Does the work which lasts few seconds
HandleRetrievedData(data);
progressNotifier.UpdateProgressBar(100);
progressNotifier.Close();
}
// Progress bar form
public partial class ProgressNotifier : Form
{
public ProgressNotifier()
{
InitializeComponent();
}
public void UpdateProgressBar(int progress)
{
backgroundWorker1.ReportProgress(progress);
progressBar_extractionProgress.Update();
}
private void backgroundWorker1_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
this.progressBar_extractionProgress.Value = e.ProgressPercentage;
}
}

Although this is an older style using delegates, you might need a check that the form is available for updating. Below is older code - there are examples using newer syntax not requiring delegates - but generally illustrates a resolve.
private delegate void StatusMessage();
/// <summary>
/// Simple methods for setting active cube list before connecting
/// </summary>
private void SetDefaultNode()
{
if (this.ActiveCubeStatus.InvokeRequired)
{
StatusMessage d = new StatusMessage(SetDefaultNodeDirect);
this.Invoke(d);
}
else
{
SetDefaultNodeDirect();
}
}
/// <summary>
/// Simple methods for setting active cube list before connecting
/// </summary>
private void SetDefaultNodeDirect()
{
//clears treeveiw
ClearActiveCubes();
//create default inactive node
TreeNode nodeDefault = new TreeNode();
nodeDefault.Name = "Waiting";
nodeDefault.Text = "Waiting on connection...";
this.ActiveCubeStatus.Nodes.Add(nodeDefault);
nodeDefault = null;
}

Related

How To Execute Code in Add-In Class After a Button Is clicked in a Forms Class?

I have this code that runs in parallel with my Form1 class code. The Global.Launcher turns into a true statement after a button is clicked from my Form class. When I run this code, I'm unable to click the add-in (Where the button is located) on Project (Basically Frozen) due to the infinite loop since I can't access the button. Is there a workaround for this? In short, I do not want to run any code in this class until after I press the button on the add-in form. Thanks in advance.
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.NewProject += new Microsoft.Office.Interop.MSProject._EProjectApp2_NewProjectEventHandler(Application_NewProject);
void Application_NewProject(Microsoft.Office.Interop.MSProject.Project pj)
{
while(Global.launcher == false)
{
}
System.Diagnostics.Debug.WriteLine("Starting to run code: ");
}
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
}

Any easy way to alert to changes in multiple text boxes?

I'm fairly new to programming so sorry if this is simple, but I might just be missing the obvious! I have the following form which is automatically populated on load from settings stored in an INI file:
The whole form is working just as I want it to apart form one small part. The 'Close' button currently just closes the form so that if any values have changed in the text boxes since the form was loaded, the changes are lost. I want to instead prompt the user to use the Save button instead otherwise the changes will be lost.
I've been trying to do something along these lines on my close button where the value of the text boxes are checked against the variable values that they were originally populated with:
private void btnClose_Click(object sender, EventArgs e)
{
if (txtName.Text != name || txtSchName.Text != schname || txtServer1.Text != svr1 etc etc etc)
{
Warn User changes will be lost, ask user if really OK to close?
if (User chooses to to close)
{
this.Close();
}
else
{
Go back to the config form;
}
}
else
{
this.Close();
}
With over 21 text fields, I was not sure if this was the most "tidy way" of checking for changes? Any pointers would be appreciated.
You just add a global boolean variable and write an handler for the TextChanged event
// Declared at the form level
private bool _modified = false;
public Form1()
{
InitializeComponent();
// This could be done in the form designer of course
// o repeated here for every textbox involved....
txtName.TextChanged += OnBoxesChanged;
......
}
private void Form1_Load(object sender, EventArgs e)
{
.....
// Code that initializes the textboxes could raise the TextChanged event
// So it is better to reset the global variable to an untouched state
_modified = false;
}
private void OnBoxesChanged(object sender, EventArgs e)
{
// Every textbox will call this event handler
_modified = true;
}
private void btnClose_Click(object sender, EventArgs e)
{
if(_modified)
{
// Save, warning, whatever in case of changes ....
}
}
Just set the event handler OnBoxesChanged for every textbox you want to trigger the condition. You could do it through the Designer or manually after the InitializeComponent call
What you are looking for is Dirty Tracking. Dirty tracking is used to track states of your control. Here is a simple reusable approach to track your controls
public class SimpleDirtyTracker
{
private Form _frmTracked;
private bool _isDirty;
public SimpleDirtyTracker(Form frm)
{
_frmTracked = frm;
AssignHandlersForControlCollection(frm.Controls);
}
// property denoting whether the tracked form is clean or dirty
public bool IsDirty
{
get { return _isDirty; }
set { _isDirty = value; }
}
// methods to make dirty or clean
public void SetAsDirty()
{
_isDirty = true;
}
public void SetAsClean()
{
_isDirty = false;
}
private void SimpleDirtyTracker_TextChanged(object sender, EventArgs e)
{
_isDirty = true;
}
private void SimpleDirtyTracker_CheckedChanged(object sender, EventArgs e)
{
_isDirty = true;
}
// recursive routine to inspect each control and assign handlers accordingly
private void AssignHandlersForControlCollection(
Control.ControlCollection coll)
{
foreach (Control c in coll)
{
if (c is TextBox)
(c as TextBox).TextChanged
+= new EventHandler(SimpleDirtyTracker_TextChanged);
if (c is CheckBox)
(c as CheckBox).CheckedChanged
+= new EventHandler(SimpleDirtyTracker_CheckedChanged);
// ... apply for other desired input types similarly ...
// recurively apply to inner collections
if (c.HasChildren)
AssignHandlersForControlCollection(c.Controls);
}
}
and in your mainform
public partial class Form1 : Form
{
private SimpleDirtyTracker _dirtyTracker;
private void Form1_Load(object sender, EventArgs e)
{
_dirtyTracker = new SimpleDirtyTracker(this);
_dirtyTracker.SetAsClean();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
// upon closing, check if the form is dirty; if so, prompt
// to save changes
if (_dirtyTracker.IsDirty)
{
DialogResult result
= (MessageBox.Show(
"Would you like to save changes before closing?"
, "Save Changes"
, MessageBoxButtons.YesNoCancel
, MessageBoxIcon.Question));
}
}
If you want to save for example usersettings you could create Settings in your Properties.
You can get them like this:
string anyProperty = WindowsFormsApplication1.Properties.Settings.Default.TestSetting;
You save them using the Save-method:
WindowsFormsApplication1.Properties.Settings.Default.TestSetting = "Hello World";
WindowsFormsApplication1.Properties.Settings.Default.Save();
You can call the Save-method after you have clicked the close-button.
For saving several string properties you could create a property of the type System.Collections.Specialized.StringCollection, so that you just create one property and not 21.
One disadvantage of dirtytracer is, that it returns "dirty" even when changes revert to original state. If You use object in DataContext as Your data model, all this task simplifies to:
bool changed = dataContext.SomeTables.GetModifiedMembers(someRow).Any();

C# Singleton form not opening correctly from Timer, opening correctly from button press

I have a singletone form that can be opened from a ribbon button or that will check every minute whether it should be open after passing a few conditional checks.
When opening the form from the ribbon button, it works correctly every time.
When opening on the timer, the form does not get rendered correctly, any place a control should be is just displayed as a white rectangle. Screenshots below.
ThisAddIn.cs
using Timer = System.Timers.Timer;
public partial class ThisAddIn
{
private Timer ticker;
private void ThisAddIn_Startup(object sender, System.EventArgs e) {
ticker = new Timer(5 * 60 * 1000);
ticker.AutoReset = true;
ticker.Elapsed += new System.Timers.ElapsedEventHandler(checkForOverdue);
ticker.Start();
}
private void checkForOverdue(object sender, System.Timers.ElapsedEventArgs e)
{
bool overdue = false;
foreach (Reminder reminder in reminders)
{
DateTime now = DateTime.Now;
if (reminder.time <= now)
{
overdue = true;
break;
}
}
if (overdue)
{
RemindersList form = RemindersList.CreateInstance();
if (form != null)
{
form.Show();
}
}
}
}
Ribbon.cs
public partial class Ribbon
{
private void reminderListButton_Click(object sender, RibbonControlEventArgs e)
{
RemindersList form = RemindersList.CreateInstance();
if (form != null)
{
form.Show();
}
}
}
RemindersList.cs
public partial class RemindersList : Form
{
private static RemindersList _singleton;
private RemindersList()
{
InitializeComponent();
this.FormClosed += new FormClosedEventHandler(f_formClosed);
}
private static void f_formClosed(object sender, FormClosedEventArgs e)
{
_singleton = null;
}
public static RemindersList CreateInstance(List<Reminder> rs)
{
if (_singleton == null)
{
_singleton = new RemindersList(rs);
_singleton.Activate();
// Flash in taskbar if not active window
FlashWindow.Flash(_singleton);
return _singleton;
}
else
{
return null;
}
}
}
EDIT - SOLUTION
Per sa_ddam213's answer, I changed out the System.Timers.Timer for a Windows.Forms.Timer and it's now working just how I wanted.
Code changes:
ThisAddIn.cs
using Timer = System.Windows.Forms.Timer;
public partial class ThisAddIn {
private void ThisAddIn_Startup(object sender, System.EventArgs e) {
ticker = new Timer();
ticker.Interval = 5 * 60 * 1000;
ticker.Tick += new EventHandler(checkForOverdue);
ticker.Start();
}
// Also needed to change the checkForOverdue prototype as follows:
private void checkForOverdue(object sender, EventArgs e)
}
You can't touch UI controls/elements with any other thread than the UI thread, in your case the System.Timer is running on another thread and the window will never open
Try switching to a Windows.Forms.Timer
Or invoke the call back to the UI thread.
private void checkForOverdue(object sender, System.Timers.ElapsedEventArgs e)
{
base.Invoke(new Action(() =>
{
/// all your code here
}));
}
I suspect that the timer event handler is not launched on the UI thread, which could cause all sorts of problems. I would check that first and ensure that the UI stuff is actually done on the UI thread.

Rate limiting on the KeyUp event handler in C#

I'm wanting to validate the input on a text field as a user types. This functionality works fine, however I would like to rate limit the validation as it's hitting an external API. I'd like to only perform a validation after a user has not typed for 750ms.
ATM I'm simply using this:
private void Configure_Load(object sender, EventArgs e)
{
endpointBox.KeyUp += EndpointBox_KeyUp;
}
void EndpointBox_KeyUp(object sender, KeyEventArgs e)
{
TestHTTP200(endpointBox.Text);
}
Use a Timer Control
System.Windows.Forms.Timer myTimer = new System.Windows.Forms.Timer();
private void Configure_Load(object sender, EventArgs e)
{
endpointBox.KeyUp += EndpointBox_KeyUp;
myTimer.Tick +=new EventHandler(OnTimedEvent); //EDIT: should not be `ElapsedEventHandler`
myTimer.Interval=750;
}
void EndpointBox_KeyUp(object sender, KeyEventArgs e)
{
myTimer.Stop();
myTimer.Start();
}
private void OnTimedEvent(Object myObject,EventArgs myEventArgs)
{
myTimer.Stop();
TestHTTP200(endpointBox.Text);
}
You would want a method equal to JavaScript's SetTimeout method. This can be cancelled when the user provides more input:
Code taken from here.
public static IDisposable SetTimeout(Action method, int delayInMilliseconds)
{
System.Timers.Timer timer = new System.Timers.Timer(delayInMilliseconds);
timer.Elapsed += (source, e) =>
{
method();
};
timer.AutoReset = false;
timer.Enabled = true;
timer.Start();
// Returns a stop handle which can be used for stopping
// the timer, if required
return timer as IDisposable;
}
You can then use this in your key up handler:
void EndpointBox_KeyUp(object sender, KeyEventArgs e)
{
IDisposable timeout = SetTimeout(() => TestHTTP200(endpointBox.Text), 750);
if (this.currentTimeout != null) {
this.currentTimeout.Dispose();
this.currentTimeout = timeout;
}
}
This is the basic principle at least, every time the user types you reinitiate a 750ms timeout to do your thing, and cancel any pending timers.
Update: complete code sample:
public partial class Form1 : Form
{
private IDisposable currentTimeout;
public Form1()
{
InitializeComponent();
}
private void EndpointBox_KeyUp(object sender, KeyEventArgs e)
{
IDisposable timeout = TimerHelper.SetTimeout(() => TestHTTP200(EndpointBox.Text), 750);
if (this.currentTimeout != null)
{
this.currentTimeout.Dispose();
this.currentTimeout = timeout;
}
}
private void TestHTTP200(string text)
{
//...
}
}
public class TimerHelper
{
public static IDisposable SetTimeout(Action method, int delayInMilliseconds)
{
System.Timers.Timer timer = new System.Timers.Timer(delayInMilliseconds);
timer.Elapsed += (source, e) =>
{
method();
};
timer.AutoReset = false;
timer.Enabled = true;
timer.Start();
// Returns a stop handle which can be used for stopping
// the timer, if required
return timer as IDisposable;
}
}
using System;
namespace Azine_Library.Misc
{
/// <summary>
/// Represents a way to delay something, eg. something an event handler does, update-ion of a control, object or to run a method, This class '<see cref="DelayUpdate"/>'
/// is intended to be used as a delayer of some sort and contains an event, '<see cref="PushUpdate"/>' that would be subcribed to an event handler where then the code
/// intended to be delayed would go. This object also contains a method, '<see cref="delay"/>' that when called delays the code written/located within the event handler
/// that is handling the '<see cref="PushUpdate"/>' event. The method, <see cref="delay"/> should be called from an event handler or a method of some sort that would orginally
/// execute the code written/located within the event handler described above also. An example of how this object can interact with another object is described and documented in great detail
/// within the documentation for "Azine_Library", also an example program is available with the documentation.
/// </summary>
public class DelayUpdate
{
// Written, 17.06.2017
#region Fields / Properties
/// <summary>
/// Occurs when the provided time (interval:) has elapsed
/// </summary>
public event EventHandler PushUpdate;
/// <summary>
/// READONLY. the amount of times the timer has ticked since the last call to delay, (DelayUpdate.delay).
/// </summary>
public int updateCounter
{
get;
private set;
}
/// <summary>
/// The amount of time [this] waits for until it pushes the update. (Milliseconds). default value: '500'.
/// </summary>
public int interval
{
get;
set;
}
/// <summary>
/// Holds the amount of times [this] raises the "DelayUpate.PushUpdate" event every call to DelayUpdate.delay() method. default value: '1'.
/// </summary>
public int updatesPerPush
{
get;
set;
}
private System.Diagnostics.Stopwatch stopWatch;
private System.Windows.Forms.Timer timer;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of type, 'DelayUpdate'; sets the classes' properties to the defaults.
/// </summary>
public DelayUpdate()
{
//Initializing Variables
this.updateCounter = 0;
this.interval = 500;
this.updatesPerPush = 1;
this.stopWatch = new System.Diagnostics.Stopwatch();
this.timer = new System.Windows.Forms.Timer();
//Sub-ing Events
this.timer.Tick += this.Timer_Tick;
}
#endregion
#region Methods
/// <summary>
/// Delays the raising of the event, PushUpdate; call this method when a property of an object has changed. eg, TextBox.TextChanged => DelayUpdate.delay();
/// </summary>
public void delay()
{
// Written, 13.06.2017
this.timer.Start();
this.stopWatch.Restart();
this.updateCounter = 0;
}
#endregion
#region Events
/// <summary>
/// Raises the 'DelayUpdate.PushUpdate' event.
/// </summary>
private void onPushUpdate()
{
//Written, 26.05.2017 : 5:22pm
if (PushUpdate != null)
PushUpdate.Invoke(this, new EventArgs());
}
#endregion
#region Event Handlers
private void Timer_Tick(object sender, EventArgs e)
{
// Written, 13.06.2017
if (this.stopWatch.ElapsedMilliseconds > this.interval)
{
if (this.updateCounter < this.updatesPerPush)
{
this.timer.Stop();
this.onPushUpdate();
}
this.updateCounter++;
}
}
#endregion
}
}
And this is how you would use this class, DelayUpdate.cs:
Say you have a WinForm that searches a directory for files which has a textbox called, "search_textBox" and has a textChanged event handler attached, :
namespace DelayUpdateExample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.search_textBox.TextChanged += this.Search_textBox_TextChanged;
}
private void Search_textBox_TextChanged(object sender, EventArgs e)
{
}
}
}
You would make a reference to the class, DelayUpdate.cs and initialize it. Subscribe to the DelayUpdate.PushUpdate event like so:
private DelayUpdate delayUpdate;
public Form1()
{
InitializeComponent();
this.delayUpdate = new DelayUpdate()
{
interval = 500,
updatesPerPush = 1,
};
this.delayUpdate.PushUpdate += this.DelayUpdate_PushUpdate;
this.search_textBox.TextChanged += this.Search_textBox_TextChanged;
}
private void DelayUpdate_PushUpdate(object sender, EventArgs e)
{
throw new NotImplementedException();
}
Within the textChanged event handler you would make a call to DelayUpdate.delay() like so..
private void Search_textBox_TextChanged(object sender, EventArgs e)
{
this.delayUpdate.delay();
}
And the code that you would want to delay would then go in the DelayUpdate_PushUpdate(object, EventArgs) that you subscribed to. like so:
private void DelayUpdate_PushUpdate(object sender, EventArgs e)
{
// You would search the directory here..
// Code that you want to delay would go here.
}

Setting a delay for an action on ListBox _SelectedIndexChanged

I have a listbox with filenames. When the selected index is changed I load the file.
I want something like jQuery's HoverIntent that delays the action of loading the file for a short time so the user can use the down arrow and quickly cycle through the items in the list without the application trying to load each one. Thread.Sleep pauses the whole app so a user can't select another list item until the sleep completes, this is obviously not what I want.
This will work if your using WinForms, make a call to the InitTimer method in the Form constructor.
Load the file in the _timer_Tick event handler. To change the delay set the Interval property in InitTimer to another value.
private System.Windows.Forms.Timer _timer;
private void InitTimer()
{
_timer = new Timer { Interval = 500 };
_timer.Tick += _timer_Tick;
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
_timer.Stop();
_timer.Start();
}
private void _timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
// TODO: Load file here
}
Use Threading to separate the loading from your GUI.
This should get you started:
public partial class MainWindow : Window
{
CancellationTokenSource cts;
bool loading;
private void SelectedIndexChanged(int index)
{
if (loading)
cts.Cancel();
cts = new CancellationTokenSource();
var loader = new Task.Delay(1000);
loader.ContinueWith(() => LoadFile(index))
.ContinueWith((x) => DisplayResult(x));
loader.Start();
}
private void DisplayResult(Task t)
{
// TODO: Invoke this Method to MainThread
if (!cts.IsCancellationRequested)
{
// Actually display this file
}
}
Could not test, as I'm still on .net 4 whereas Task.Delay() is .net 4.5
You may need to add another field in the form for the file content transfer from the tasks to the GUI.
Winforms:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private CancellationTokenSource _cancel;
private object _loadLock = new object();
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
lock (_loadLock)
{
handleCancellation();
var loader = new Task((chosenFileItemInListbox) =>
{
Thread.Sleep(1000);
LoadFile(chosenFileItemInListbox);
}, listBox1.SelectedItem, _cancel.Token);
}
}
private bool handleCancellation()
{
bool cancelled = false;
lock (_loadLock)
{
if (_cancel != null)
{
if (!_cancel.IsCancellationRequested)
{
_cancel.Cancel();
cancelled = true;
}
_cancel = null;
}
}
return cancelled;
}
private void LoadFile(object chosenFileItemInListbox)
{
if (handleCancellation())
{
return;
}
}
}
The code above could also be applied to WPF, but WPF contains some built in magic for handling delays and cancellation of previous updates.
<ListBox SelectedItem="{Binding Path=SelectedFile, Delay=1000}" />

Categories

Resources