How to restore minimized form - winforms

This is such a mundane issue I thought I could fix it immediately, but no.
My form size and location are saved in app settings on exit for restore next time the app is run. If the user closes the form while it is minimized I am having trouble restoring to normal state. The form restores as minimized, and clicking on the taskbar button does nothing. I save the location and size in FormClosing event, but if the form is minimized I am saving the minimized size (160, 40) and location (-32000, -32000), which is totally incorrect for restoring the form. I want to force the form to never restore minimized, but to it's last normal size and location. Somehow I must capture the size and location before the form is minimized and save that, and then on FormClosing do not save size and location if the form is minimized. This is all probably not 100% clear, but I hope someone has some insight on this.
FormClosing handler:
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
Settings.Default.WindowLocation = Location;
Settings.Default.WindowSize = Size;
Settings.Default.WindowState = WindowState;
Settings.Default.Save();
}
Restoring code:
private void RestoreWindow()
{
Location = Settings.Default.WindowLocation;
if(Location.X == 0 && Location.Y == 0)
StartPosition = FormStartPosition.CenterScreen;
Size = Settings.Default.WindowSize;
WindowState = FormWindowState.Normal;
if(Size.Width > Screen.PrimaryScreen.WorkingArea.Width)
{
Location = new Point(0, Location.Y);
Size = new Size(Screen.PrimaryScreen.WorkingArea.Width, Size.Height);
}
if(Size.Height > Screen.PrimaryScreen.WorkingArea.Height)
{
Location = new Point(Location.X, 0);
Size = new Size(Size.Width, Screen.PrimaryScreen.WorkingArea.Height);
}
}

You shouldn't save the Location or Size of your form if it isn't in the normal state:
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (this.WindowState == FormWindowState.Normal) {
Settings.Default.WindowLocation = Location;
Settings.Default.WindowSize = Size;
}
Settings.Default.WindowState = WindowState;
Settings.Default.Save();
}
Your Restore window routine doesn't make complete sense. Why save the location if you are centering the form? Starting a program in Minimized mode is probably undesirable, in which case, I would default it to Normal:
private void RestoreWindow()
{
this.Location = Settings.Default.WindowLocation;
this.Size = Settings.Default.WindowSize;
// check for size or location off-screen, etc.
if ((FormWindowState)Settings.Default.WindowState == FormWindowState.Minimized)
this.WindowState = FormWindowState.Normal;
else
this.WindowState = Settings.Default.WindowState;
}
If you need to restore the last normal position the window was in, then you can use the OnResizeEnd override to save the settings:
protected override void OnResizeEnd(EventArgs e) {
if (this.WindowState == FormWindowState.Normal) {
Properties.Settings.Default.Location = this.Location;
Properties.Settings.Default.Size = this.Size;
}
base.OnResizeEnd(e);
}
Then your closing event is just:
protected override void OnFormClosing(FormClosingEventArgs e) {
Properties.Settings.Default.WindowState = this.WindowState;
Properties.Settings.Default.Save();
base.OnFormClosing(e);
}

Store the form's size and location in local variables in the Form.Resize event handler and Form.Move event handler respectively, only if the Form.WindowState property is different than FormWindowState.Minimized. Then save the content of the size and location variables in the settings, in FormClosing event handler.

Related

Use a timer to update contents of Notifyicon text

I am having issues updating the text in a tooltip for a system tray application. I have a timer updating several strings, but it will not update the notifyicon text. Here is what I have tried.
//This is in my main
System.Timers.Timer aTimer = new System.Timers.Timer();
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
aTimer.Interval = 5000;
aTimer.Enabled = true;
}
private static void OnTimedEvent(object sender, ElapsedEventArgs e)
{
ProcessIcon.GetDNS();
ProcessIcon.GetIP();
ProcessIcon.getMAC();
ProcessIcon.showALL();
Fixes.SetNotifyIconText(ni, ProcessIcon.showALL()); //This one will not update, the others update fine.
}//End part of my main
//This part is in another class for the notifyicon part.
public void Display()
{
// Put the icon in the system tray and allow it react to mouse clicks.
ni.MouseClick += new MouseEventHandler(ni_MouseClick1);
ni.Icon = new Icon("WhoAmI.ico");
Fixes.SetNotifyIconText(ni, showALL());
ni.Visible = true;
// Attach a context menu.
ni.ContextMenuStrip = new ContextMenus().Create();
}
I removed the timer and now call each function when the user hovers over the icon.

Working with ProgressBar and ComboBox

I'm in trouble with a Marquee ProgressBar. I need to execute a method (refreshList()) to get a List<string>. Then I assign this List to a ComboBox, so ComboBox refreshes with the new Items. As refreshList() take 3 or 4 sec, I wanted to run a Marquee ProgressBar. But I couldn't. ProgressBar is ok, but ComboBox doesn't load new Items.
My refreshList() method:
private void refreshList(List<string> list)
{
albumList.DataSource = null;
albumList.DataSource = list;
}
I have the following code, it works fine:
private void changeDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
fbd.RootFolder = Environment.SpecialFolder.MyComputer;
folderPath = "";
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
folderPath = fbd.SelectedPath;
refreshList(N.getList(folderPath));
}
}
But I added a ProgressBar and wrote this code:
private void changeDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
fbd.RootFolder = Environment.SpecialFolder.MyComputer;
folderPath = "";
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
folderPath = fbd.SelectedPath;
bgWorker.WorkerReportsProgress = true;
bgWorker.RunWorkerAsync();
}
}
And I placed refreshList() in doWork() method:
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
refreshList(N.getList(folderPath));
}
But unfortunately this isn't working. Can anybody help me solving this problem? Thanks in advance.
You can use the MarqueeAnimationSpeed and Value properties of the ProgressBar control to stop and start the Marquee. There's no need to use WorkerReportsProgress* as you aren't incrementing a normal progress bar - you just want to "spin" the Marquee.
You can do something like the following:
public Form1()
{
InitializeComponent();
//Stop the progress bar to begin with
progressBar1.MarqueeAnimationSpeed = 0;
//If you wire up the event handler in the Designer, then you don't need
//the following line of code (the designer adds it to InitializeComponent)
//backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
}
private void changeDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
fbd.RootFolder = Environment.SpecialFolder.MyComputer;
folderPath = "";
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
folderPath = fbd.SelectedPath;
//This line effectively starts the progress bar
progressBar1.MarqueeAnimationSpeed = 10;
bgWorker.RunWorkerAsync(); //Calls the DoWork event
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = N.getList(folderPath); //Technically this is the only work you need to do in the background
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//these two lines effectively stop the progress bar
progressBar1.Value = 0;
progressBar1.MarqueeAnimationSpeed = 0;
//Now update the list with the result from the work done on the background thread
RefreshList(e.Result as List<String>);
}
private void RefreshList(List<String> results)
{
albumList.DataSource = null; //You don't need this line but there is no real harm.
albumList.DataSource = list;
}
Remember to wire up the RunWorkerCompleted event to backgroundWorker1_RunWorkerCompleted via the Properties bar, Events section in the designer.
To begin with, we start the ProgressBar's animation by setting the MarqueeAnimationSpeed property to a non-zero positive number as part of your successful folder selection.
Then, after calling RunWorkerAsync, the code builds your list in the DoWork method, then assigns the result to the DoWorkEventArgs, which get passed to the RunWorkerCompleted event (which fires when DoWork is finished).
In the backgroundWorker1_RunWorkerCompleted method, we stop the progress bar (and set it's value to zero to effectively return it to it's original state), and then we pass the list to the refreshList method to databind it and populate the ComboBox.
Tested using VS2012, Windows Forms, .Net 4.0 (with a Thread.Sleep to emulate the time taken for N.getList)
*WorkerReportsProgress, and the associated ReportProgress method/event are used when you want to increment the progress bar - you can tell the GUI that you are 10% done, 20% done, 50% done etc etc.

Click on treeview node open a new MDI form, focus left on first form

I'm trying to open a new form after clicking a node in a treeview.
In the first MDI form I have a treeview, when I click on a node in the tree view a 2nd MDI form is opened, but the first form keeps the focus. I want the new form to have the focus.
I have noticed the first form's _Enter event is firing as if something is setting focus back to the first form.
There is also a button on the first form that does the same function and it works great. I have a feeling the treeview has some special attribute set to cause the focus to come back to the first form.
Here is the code opening the form
private void tvClientsAccounts_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
OpenClientOrAccount(e.Node);
}
private void OpenClientOrAccount(TreeNode Node)
{
if (Node.Tag is Client)
{
frmClients frmC = new frmClients();
Client Client = (Client)Node.Tag;
frmC.Id = Client.Id;
frmC.HouseholdId = Client.Household.Id;
frmC.MdiParent = Program.frmMain;
frmC.Show();
}
else if (Node.Tag is Account)
{
frmAccounts frmA = new frmAccounts();
Account Account = (Account)Node.Tag;
frmA.Id = Account.Id;
frmA.ClientId = Account.Client.Id;
frmA.MdiParent = Program.frmMain;
frmA.Show();
}
}
Here is the designer code defining the treeview
//
// tvClientsAccounts
//
this.tvClientsAccounts.BackColor = System.Drawing.SystemColors.Control;
this.tvClientsAccounts.Indent = 15;
this.tvClientsAccounts.LineColor = System.Drawing.Color.DarkGray;
this.tvClientsAccounts.Location = new System.Drawing.Point(228, 193);
this.tvClientsAccounts.Name = "tvClientsAccounts";
this.tvClientsAccounts.Nodes.AddRange(new System.Windows.Forms.TreeNode[] {
treeNode9});
this.tvClientsAccounts.PathSeparator = "";
this.tvClientsAccounts.ShowNodeToolTips = true;
this.tvClientsAccounts.Size = new System.Drawing.Size(411, 213);
this.tvClientsAccounts.TabIndex = 23;
this.tvClientsAccounts.BeforeExpand += new System.Windows.Forms.TreeViewCancelEventHandler(this.tvClientsAccounts_BeforeExpand);
this.tvClientsAccounts.AfterExpand += new System.Windows.Forms.TreeViewEventHandler(this.tvClientsAccounts_AfterExpand);
this.tvClientsAccounts.BeforeSelect += new System.Windows.Forms.TreeViewCancelEventHandler(this.tvClientsAccounts_BeforeSelect);
this.tvClientsAccounts.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.tvClientsAccounts_AfterSelect);
this.tvClientsAccounts.NodeMouseClick += new System.Windows.Forms.TreeNodeMouseClickEventHandler(this.tvClientsAccounts_NodeMouseClick);
Thanks for your help
Russ
Yes, TreeView is a bit of a pain that way, it restores the focus to itself. That's why it has the AfterXxx events, but there's no AfterNodeMouseClick event. The way to solve it is to delay executing the method, until after all event side-effects are completed. That's elegantly done by using the Control.BeginInvoke() method, its delegate target runs when the UI thread goes idle again. Like this:
private void treeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) {
this.BeginInvoke(new Action(() => OpenClientOrAccount(e.Node)));
}

Memory Leak in WPF

I wrote a very simple newbie app with a 6-sided polyhedron (a "box") which rotates 180 degrees when I click a button. and then rotates back again on the next click. Every rotation grabs another 90MB and it doesn't let go until I close the app. The box is defined in the XAML. The Storyboard, DoubleAnimation and PropertyPath, etc, are all created ONCE, in the constructor. The button code looks like this:
private void button_Storyboard1_Click(object sender, RoutedEventArgs e)
{
GC.Collect();
if (_bFront)
{
_myDoubleAnimation.From = 0;
_myDoubleAnimation.To = 180;
_bFront = false;
}
else
{
_myDoubleAnimation.From = 180;
_myDoubleAnimation.To = 0;
_bFront = true;
}
_myDoubleAnimation.Duration = _Duration;
Storyboard.SetTargetName(_myDoubleAnimation, "rotate_me");
Storyboard.SetTargetProperty(_myDoubleAnimation, _PropP);
_sb.Children.Add(_myDoubleAnimation);
_sb.Begin(this.viewport3D1);
}
After a few rotations I'm out of memory! What's going on?
Could be totally wrong here, but aren't you adding _myDoubleAnimation to _sb.Children on each click, instead of just updating it?

Drift when restoring window location and size in WPF

I am using the code below to save and restore the window position and size upon restart.
I am observing an upward drift of 28 pixels everytime I execute this code!
Am I reading the wrong values, or am I restoring them incorrectly? Where is the number 28 (size of the chrome?) coming from (and how would I account for it programmatically, rather than a fixed number in code)?
Here is my code:
public partial class MainStudioWindowControl : RibbonWindow
{
public MainStudioWindowControl()
{
App.MainWindowOwner = this;
this.Loaded += new System.Windows.RoutedEventHandler(MainStudioWindowControl_Loaded);
}
void MainStudioWindowControl_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
System.Windows.Window mainWindow = System.Windows.Application.Current.MainWindow;
mainWindow.WindowStartupLocation = System.Windows.WindowStartupLocation.Manual;
if (Studio.Properties.Settings.Default.Width > 0)
{
mainWindow.Left = Studio.Properties.Settings.Default.Left;
mainWindow.Top = Studio.Properties.Settings.Default.Top;
mainWindow.Width = Studio.Properties.Settings.Default.Width;
mainWindow.Height = Studio.Properties.Settings.Default.Height;
}
Debug.WriteLine(string.Format("Loading: Top = {0}", this.Top));
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
System.Windows.Window mainWindow = System.Windows.Application.Current.MainWindow;
Studio.Properties.Settings.Default.Left = mainWindow.Left;
Studio.Properties.Settings.Default.Top = mainWindow.Top;
Studio.Properties.Settings.Default.Width = mainWindow.Width;
Studio.Properties.Settings.Default.Height = mainWindow.Height;
Studio.Properties.Settings.Default.Save();
Debug.WriteLine(string.Format("Saving: Settings.Top = {0}", Studio.Properties.Settings.Default.Top));
}
}
Try this:
1) Derive your class from the normal Window, not the RibbonWindow - if that fixes it, it's a RibbonWindow issue.
2) Use hard-coded values to set the measurments in the Loaded handler - if that fixes it, the problem's got something to do with the settings.
With these two changes, it worked fine for me. The window appeared where it should every time.

Resources