My application has the following UI configuration:
The main form is an MDI container. Its child forms are attached to a TabStrip.
Each user has his set of child forms. Depending on the active user, only that user's child forms are displayed, together with tabs.
This is achieved by going through the main form's MdiChildren and setting their Visible property to false/true depending on the active user.
foreach (Form item in MdiChildren)
{
if (((OfficeFormEx)item).UserID == (int)e.NewTab.Tag)
{
item.Visible = true;
}
else
{
item.Visible = false;
}
}
This has two undesired effects. One is that every child form gets redrawn in succession, which is ugly and slow. The other is that for some reason the forms go from maximized to normal, effectively undocking them from the main form.
Is there any way to display just one of the child forms, such as the one the user was previously looking at, and get the others to stay in the background? The maximize/normal thing is not that big a deal because I can maximize them again manually.
Your question isn't very clear without a code snippet. You are however doing battle with the Windows MDI implementation. One thing it doesn't support is hiding a child window, it can only be minimized at best. Windows Forms implements the Visible property by destroying the Window handle, recreating it when the Visible property is set to True again. That new instance of the window won't be maximized.
It also doesn't support switching the focus to a child window when the current one is maximized. WF's workaround for that is to force the active child window back to the Normal state.
The MDI model is simply not very suitable for displaying child windows in the maximized state. To get a tabbed interface, use a TabControl and display UserControls on its tab pages.
At first sight, I would take a look at the Form.WindowsState property, if you haven't already. I doubt that if you happen to set this property to FormWindowState.Maximized on design, this would be changed when setting their Visible property true/false.
For the "[...] every child form gets redrawn in succession [...]"-thing, have you tried to use the SuspendLayout() method on the beginning of the active user's forms verification, and calling ResumeLayout() afterwards?
EDIT #1
I would advise you to load only the required Form for the current user.
Doing so will reduce the amount of memory used by your application, plus, it shall reduce considerably the number of forms contained within the MdiChildren collection property. Then, iterating through the collection, if still required, will be faster.
If this isn't an option for you, then perhaps using Linq might help:
var visibleForms = from f in MdiChildren
where (((OfficeFormEx)f).UserID == (int)e.NewTab.Tag)
select f;
var invisibleForms = from f in MdiChildren
where (((OfficeFormEx)f).UserID != (int)e.NewTab.Tag)
select f
visibleForms.ToList().ForEach(f => f.Visible = true);
invisibleForms.ToList().ForEach(f => f.Visible = false);
If you're using .NET 4.0, perhaps would this be a good candidate for PLINQ
Please provide feedback so that we can come to a solution. =)
I eventually solved this one so here's a belated write-up.
Will Marcouiller suggested SuspendLayout() and ResumeLayout(), which did not work. This led me to investigate what these two methods actually do and reach the conclusion that what I needed was a way to stop the main form from redrawing while operations on the MDI children were in progress.
This in turn resulted in the following two static utility methods which suspend redrawing for a given control. In my case, suspending redrawing of the main form resulted in a massive speed up.
/// <summary>
/// suspends drawing on a control and its children
/// </summary>
/// <param name="parent"></param>
public static void SuspendDrawing(Control control)
{
SendMessage(control.Handle, WM_SETREDRAW, false, 0);
}
/// <summary>
/// resumes drawing on a control and its children
/// </summary>
/// <param name="parent"></param>
public static void ResumeDrawing(Control control)
{
SendMessage(control.Handle, WM_SETREDRAW, true, 0);
control.Refresh();
}
Related
I have a very basic out the box mdiparent which has a number of mdichildren and a menu item. Each button on the menu item hides all the forms and then shows the one respective to that button.
When I do it this way:
//dontHide is the Form we want to show.
for(int i = 0; i < this.MdiChildren.Length; i++)
{
if (this.MdiChildren[i] != dontHide)
{
this.MdiChildren[i].Visible = false;
}
}
dontHide.Visible = true;
Switching forms causes the new form opened to be positioned bit lower and to the right of the old form, but clicking the menu item for the currently displayed form does nothing (as expected).
But, when I do this:
//dontHide is the Form we want to show.
for(int i = 0; i < this.MdiChildren.Length; i++)
{
this.MdiChildren[i].Visible = false;
}
dontHide.Visible = true;
Even clicking the menu item for the currently visible form causes it to shift to the lower right, same as opening a new form. Why is that?
Edit:
I've also noticed when centering the form and then displaying it (so you don't risk having someone glimpse it right before it is moved), setting visible to true completely resets any centering I've done.
This is caused by an obscure implementation detail in Winforms. The native MDI support built into Windows does not support hiding child windows. Winforms works around this restriction by destroying the child window when you set its Visible property to false. And re-creating it when you set it back to true.
This can have various side-effects, the state of the native window is lost when this happens of course. Winforms has fairly decent support for restoring the window again from its properties. But one thing it doesn't do is recreating the window in the same location. So you'll see it getting recreated in the location that new MDI child windows get, staggered from the previous window. Whether that was an oversight or intentional isn't that clear to me, 95% odds for the latter.
Otherwise simple to work around, you can assign the Location property yourself to get it back where it was:
var loc = dontHide.Location;
dontHide.Visible = true;
dontHide.Location = loc;
Or just set the MDI child form's StartPosition to Manual.
I am almost certainly missing something completely obvious. I have a singleton main form with various menu items with shortcuts. I have child windows which can be embedded on the main form or floating on their own. The main form class has a static member that points to the one extant main form, so the child windows can access its public functions. I want the hotkeys normally linked to the toolbar entries to work from the child windows, and I'd prefer not to duplicate the code. I know I have to invoke a keypress event on the main form, but I am running a complete blank today.
To give a simple example, there are menu items to save the current file and to center the window on the point the mouse is at, triggered by Ctrl+S and Ctrl+E respectively. They're set up as keyboard shortcuts in my Main Form, but the actual heavy lifting is done by my child window for the latter command. I have it temporarily fixed by catching a KeyDown event in my child window, but that means two different places that the same shortcut shows up.
There seem to be few solutions to handle key shortcuts.
One of them could be installing keyboard hooks as suggested here.
You can also try to handle this by adding custom message filter, as suggested here, however, I haven't verified the code posted there.
The solution with hooks seems to be a little tricky, so You may want to try custom message filter first.
As per my comments to Lukasz M above, I needed to maintain the current menu structure for legacy reasons, namely that all of the engineers are accustomed to changing the Menu, and they want the menu shortcuts to automatically work. I could probably modify the central hotkey location using that custom message filter to generate Shortcut text for the Menu items, but that would add additional complexity that's likely to be undone the next time someone steps in to add a quick menu item. Thus, I went with the solution I alluded to in the comments with an invisible menu for the child windows.
Much to my surprise, if a menu has its Visible property set to false, the hotkeys work just fine. And the events associated with menu items in the main form work perfectly fine if invoked from a child form, as they're executed relative to the window where they are defined rather than by the window where they're invoked. Thus, my first solution was to fetch the MenuStrip from the main form and add it to the child form. That didn't work, as turning it invisible in the child form made it invisible in the main form as well. My next attempt involved creating a new hidden MenuStrip and adding the ToolStripMenuItem items from the main form's MenuStrip onto it. That broke the hotkey capability, possibly since the menu items now existed in more than one place. Finally, I created shallow copies of the menu items that contained only the shortcut key, the Tag property (necessary for a few menu items that made use of it), and the Event Handler (the last being done through the method described in How to clone Control event handlers at run time?). After a bit of fiddling, I came to realize that I didn't need to maintain the structure of the menus and I only needed the items with shortcuts. This is what I wound up with:
Main Form:
/// <summary>
/// Returns copies of all menu shortcut items in the main form.
/// </summary>
/// <returns>A list containing copies of all of the menu items with a keyboard shortcut.</returns>
public static List<ToolStripMenuItem> GetMenuShortcutClones()
{
List<ToolStripMenuItem> shortcutItems = new List<ToolStripMenuItem>();
Stack<ToolStripMenuItem> itemsToBeParsed = new Stack<ToolStripMenuItem>();
foreach (ToolStripItem menuItem in mainForm.menuStrip.Items)
{
if (menuItem is ToolStripMenuItem)
{
itemsToBeParsed.Push((ToolStripMenuItem)menuItem);
}
}
while (itemsToBeParsed.Count > 0)
{
ToolStripMenuItem menuItem = itemsToBeParsed.Pop();
foreach (ToolStripItem childItem in menuItem.DropDownItems)
{
if (childItem is ToolStripMenuItem)
{
itemsToBeParsed.Push((ToolStripMenuItem)childItem);
}
}
if (menuItem.ShortcutKeys != Keys.None)
{
shortcutItems.Add(CloneMenuItem(menuItem));
}
}
return shortcutItems;
}
/// <summary>
/// Returns an effective shortcut clone of a ToolStripMenuItem. It does not copy the name
/// or text, but it does copy the shortcut and the events associated with the menu item.
/// </summary>
/// <param name="menuItem">The MenuItem to be cloned</param>
/// <returns>The newly generated clone.</returns>
private static ToolStripMenuItem CloneMenuItem(ToolStripMenuItem menuItem)
{
ToolStripMenuItem copy = new ToolStripMenuItem();
copy.ShortcutKeys = menuItem.ShortcutKeys;
copy.Tag = menuItem.Tag;
var eventsField = typeof(Component).GetField("events", BindingFlags.NonPublic | BindingFlags.Instance);
var eventHandlerList = eventsField.GetValue(menuItem);
eventsField.SetValue(copy, eventHandlerList);
return copy;
}
Child Form:
private void OnRefresh(object sender, EventArgs e)
{
// Refresh the hiddenShortcutMenu.
List<ToolStripMenuItem> shortcutList = MainForm.GetMenuShortcutClones();
hiddenShortcutMenu.Items.Clear();
hiddenShortcutMenu.Items.AddRange(shortcutList.ToArray());
}
Inside the child form's constructor, I instantiated the hiddenShortcutMenu, set Visible to false, assigned it to my child form's control, and set the event. The last was a bit fiddly in that I had to have it periodically refresh since the menus sometimes changed according to context. Currently, I have it set to the Paint event for maximum paranoia, but I think I'm going to try to find a way for the main form to signal that it's changed the menu structure.
I'm trying to put an icon in the system tray and then give it a global keyboard shortcut to carry out a function.
I'm using RegisterHotKey to set the global keyboard shortcut, and it works if the main form associated with the icon is visible. But if the form is invisible then the WndProc method is never invoked.
Any ideas?
Edit:
What I mean by "hidden" is that the following is added to the main form:
protected override void OnLoad(EventArgs e)
{
hotKey = new GlobalHotkey(GlobalHotkey.WIN, Keys.T, this);
bool registered = hotKey.Register();
Visible = false;
ShowInTaskbar = false;
base.OnLoad(e);
}
"registered" is showing as "true", and the shortcut key works fine if I leave out the "Visible = false;" and the "ShowInTaskbar = false;".
The problem is that setting ShowInTaskbar to false changes the window handle, which means that the hwnd passed to RegisterHotkey is no longer valid.
Registering the hotkey after setting ShowInTaskBar works fine.
Winforms works around a pretty draconian restriction in the winapi. Some properties of a window can only be specified when a window is created and can't be changed later. Or in other words, they are specified in the native CreateWindowEx() call.
It works around it by calling CreateWindowEx() again. Or in other words, destroy the existing window and create it again. That's a nifty trick but it does have some side effects. You can see a wee bit of flicker for example when the new window paints itself. Some bigger side effects are visible on for example a TreeView. All the nodes collapse when it gets recreated. Hard to avoid, there is just too much state associated with the original window. For a Form, the ShowInTaskbar property is one such property. But also RightToLeft, FormBorderStyle, ControlBox, etcetera.
The most relevant side-effect is the one you are running into. Recreating the window always changes the Handle property, inevitably. And that goes wrong when you use RegisterHotKey(), or a library that uses it, that winapi call uses the window handle. So when Winforms destroys that window there will never again be a callback.
It is easy to fix, you are just using the wrong event handler. Make the call in an override for the OnHandleCreated method instead. It re-runs when the window gets re-created. Yet another easy fix, but not nearly as reliable, is to only set properties like ShowInTaskbar in the constructor.
Is it possible for Owner window in WPF be on top of Child window when you click on it while Owner window is below Child window?
here is example how I call child window:
Window2 window = new Window2();
window.Owner = this;
window.Show();
Parent/Owner window will always be under child window.
To get the behavior you want, you don't want to set the Owner on either window.
You, of course will have to handle the logic yourself when closing either of the windows to close your imaginary "child" window.
There may be some other logic you'll have to implement related to minimizing, maximizing, etc.
Many of the answers on this page involve nulling-out the Window.Owner property for some or all of the (non-MainWindow) windows in your System.Windows.Application. While this is a simple and easy fix that does indeed, in isolation, fix the issue of Window overlap, unfortunately it also inhibits a lot of useful application-wide functionality that WPF seems otherwise eager to provide in the areas of:
Application activation/deactivation (via mouse-click, desktop Alt-Tab switching, etc...),
correct observance of the Application.ShutdownMode property, and generally,
orderly cleanup, resource disposal, and exit of your Application upon shutdown.
It is possible fix the Window overlap issue while still preserving these system-wide WPF features by instead designating a special invisible window instance as your Application.MainWindow.
Modify your application so that the first Window it creates--which is the Window that gets assigned to Application.MainWindow--is a special dummy Window that is then made invisible by setting its Visibility to Visibility.Hidden or calling Window.Hide(). This will not hide its owned windows. Then, ensure that your desired "main" window containing your true content, plus all the other windows, owned by this invisible window.
Once hidden, the dummy Window will not show in the Windows 10 taskbar. You can set the Window.ShowInTaskbar property on whichever of the visible windows you deem appropriate to simulate apparent special designation, as required.
This approach allows any of the visible windows in your Application to be on top of the others, while still preserving WPF features and behaviors for system-wide app activation. For example, when the Application is activated (by clicking on any one of the windows, or via Alt-tab), all of the application's windows are together brought above any other desktop app windows, while still preserving the most recent "in-app" Z-order. WPF shutdown functionality is also preserved, including correct observation of the Application.ShutdownMode logic in accordance with the invisible MainWindow (or all the others) being closed.
I ran into a similar situation. I solved this by simply removing the owner after showing the window.
Window2 window = new Window2();
window.Owner = this;
window.Show();
window.Owner = null;
Edit:
Someone replied to this, and while looking at it, I decided I wanted to make an extension method.
public static void ShowAsIfChildOf(this Window childWindow, Window parentWindow)
{
childWindow.Owner = parentWindow;
childWindow.Show();
childWindow.Owner = null;
}
Please note that the solution mentioned by Licht
Window2 window = new Window2();
window.Owner = this;
window.Show();
window.Owner = null;
seems to me to be the same as
Window2 window = new Window2();
window.Show();
i.e., as if no ownership relationship has been set, i.e., when you close the owner window the owned windows do not close, etc., and may not be a solution when one would wish to have all of the ownership relationship features except "An owner window can never cover an owned window."
Once this relationship is established, the following behaviours are exhibited:
If an owner window is minimized, all its owned windows are minimized as well.
If an owned window is minimized, its owner is not minimized.
If an owner window is maximized, both the owner window and its owned windows are restored.
An owner window can never cover an owned window.
Owned windows that were not opened using ShowDialog are not modal. The user can still interact with the owner window.
If you close an owner window, its owned windows are also closed.
The best solution here looks to me to be the one with hidden main dummy window described by Glenn Slayden, at the link below, though it would be nice if there was a simpler one.
https://stackoverflow.com/a/66110288/19683309
Don't set Owner for child window, then in MainWindow handle OnClosing event like this:
private void MainWindow_OnClosing(object? sender, CancelEventArgs e)
{
foreach (var window in Application.Current.Windows)
{
if (window is Window appWindow)
{
if(appWindow.Equals(Application.Current.MainWindow))
continue;
appWindow.Close();
}
}
}
I've decided to reimplement the datetime picker, as a standard datetime picker isn't nullable. The user wants to start with a blank field and type (not select) the date.
I've created a user control to do just that, but if the user control is near the edge of the form, it will be cut off on the form boundry. The standard datetime picker doesn't suffer from this problem.
Here is a picture showing the problem. My user control is on the left, the standard datetimepicker is on the right:
alt text http://img50.imageshack.us/img50/9104/datetimepickervu6.jpg
As you can see, the standard control will display over the form AND application boundry. How do I get the month picker in my control to do the same thing?
Thanks!
The ToolStripDropDown control has this functionallity so by inheriting from it we can make a simple PopupWindow.
/// <summary>
/// A simple popup window that can host any System.Windows.Forms.Control
/// </summary>
public class PopupWindow : System.Windows.Forms.ToolStripDropDown
{
private System.Windows.Forms.Control _content;
private System.Windows.Forms.ToolStripControlHost _host;
public PopupWindow(System.Windows.Forms.Control content)
{
//Basic setup...
this.AutoSize = false;
this.DoubleBuffered = true;
this.ResizeRedraw = true;
this._content = content;
this._host = new System.Windows.Forms.ToolStripControlHost(content);
//Positioning and Sizing
this.MinimumSize = content.MinimumSize;
this.MaximumSize = content.Size;
this.Size = content.Size;
content.Location = Point.Empty;
//Add the host to the list
this.Items.Add(this._host);
}
}
Usage:
PopupWindow popup = new PopupWindow(MyControlToHost);
popup.Show(new Point(100,100));
...
popup.Close();
The screenshots looks like a Windows Forms applications, so my answer is for winforms.
I guess the best solution would be to create a customcontrol that itself uses the datetime picker that already has the behavior.
Show a empty textbox until it gets clicked, then display the datetimepicker.
That would save you a bunch of code..
I ran into this when trying to implement a custom control and discovered that it's a remarkably hard problem. There's no built-in functionality within the Windows.Forms model to support controls whose display area extends outside the client area of their container.
You basically have to either use the Windows API or draw your controls inside a Form with AlwaysOnTop set. Both approaches are harder than they should be. I ended up redesigning my control so that instead of displaying its expanded contents in a dropdown it used a modal dialog. This was a pretty unsatisfying solution, but I spent a couple of weeks trying other approaches and could never get anything that worked consistently across all use cases (like disappearing when the application loses focus).
I'm not 100% sure, but a quick look at the DateTimePicker class on Reflector takes me to the SafeNativeMethods.SetWindowPos internal class.
You can override the SetBoundsCore from the base Control class or, like Tigraine stated, create a custom control based on the DateTimePicker.
Hope it helps,
Bruno Figueiredo
The reason that your control gets chopped off is because it is a child control of the form that you reside on. Any control on the form must be contained by the form, hence it gets chopped off.
I haven't done this in .Net, but had a similar problem in VB6. The solution then was to set the parent of the popup window (the calendar in your case) to be the desktop. This will allow it to extend beyond the boundaries of your form. You'll have to do some P/Invoke magic to find the hWnd of the popup, and another P/Invoke to set the parent.