I have followed this https://rjcodeadvance.com/iu-moderno-temas-multicolor-aleatorio-resaltar-boton-form-activo-winform-c/ to create a Windows Forms user interface for an application I am planning.
I am trying to enable a button in the parent form based on the results of a successful login from a child form.
I have managed to do it, and it works, with this code. When login is successful, the button in the parent form is enabled.
private void OpenChildForm(Form childForm, object btnSender)
{
if (activeForm != null)
activeForm.Close();
ActivateButton(btnSender);
activeForm = childForm;
childForm.TopLevel = false;
childForm.FormBorderStyle = FormBorderStyle.None;
childForm.Dock = DockStyle.Fill;
this.panelDesktopPane.Controls.Add(childForm);
this.panelDesktopPane.Tag = childForm;
childForm.BringToFront();
FormLogin frmlgn = new FormLogin();
frmlgn.DataAvailable += new EventHandler(child_DataAvailable);
frmlgn.Show();
lblTitle.Text = childForm.Text;
btnClose.Visible = true;
btnMaximize.Visible = true;
btnMinimize.Visible = true;
btnClose.BringToFront();
btnMaximize.BringToFront();
btnMinimize.BringToFront();
}
The problem I am facing is that the child form isn't shown inside a panel anymore, which breaks the user interface.
As you can guess, the method performs several modifications to show the form passed as a parameter inside the panel so everything is shown in the same "window".
If I change frmlgn.Show(); to childForm.Show();, I break the EventHandler mechanism, and the button is not enabled, although I keep the user interface as I would like to. When I run the code, as shown above, event handling works but the Login form is shown as a separate window, instead of inside the main window.
I can't figure out how to keep the user interface intact while transferring the data to enable the button.
Everything I try shows some kind of error while compiling. The closest I have been is trying to change frmlgn.DataAvailable += new EventHandler(child_DataAvailable); to childForm.DataAvailable += new EventHandler(child_DataAvailable) but it says *CS1061 'Form' does not contain a definition for 'DataAvailable' and no accessible extension method ... *
Any suggestion?
Thanks.
Update!!
Well I have managed to get it working, although I would like to have a "cleaner" approach, if possible.
I have just created an if loop that executes the modifications to the particular Login form and then shows it in the panel. If the Form to load is different from Login then it executes the code as "is". Thankfully it works, but I don't feel comfortable with this little trick. I am sure there has to be a clean way of doing this.
private void OpenChildForm(Form childForm, object btnSender)
{
if (childForm.Name == "FormLogin")
{
FormLogin frmlgn = new FormLogin();
if (activeForm != null)
activeForm.Close();
ActivateButton(btnSender);
activeForm = childForm;
frmlgn.TopLevel = false;
frmlgn.FormBorderStyle = FormBorderStyle.None;
frmlgn.Dock = DockStyle.Fill;
panelDesktopPane.Controls.Add(frmlgn);
this.panelDesktopPane.Tag = childForm;
frmlgn.BringToFront();
frmlgn.DataAvailable += new EventHandler(child_DataAvailable);
frmlgn.Show();
}
else
{
if (activeForm != null)
activeForm.Close();
ActivateButton(btnSender);
activeForm = childForm;
childForm.TopLevel = false;
childForm.FormBorderStyle = FormBorderStyle.None;
childForm.Dock = DockStyle.Fill;
this.panelDesktopPane.Controls.Add(childForm);
this.panelDesktopPane.Tag = childForm;
childForm.BringToFront();
childForm.Show();
}
lblTitle.Text = childForm.Text;
btnClose.Visible = true;
btnMaximize.Visible = true;
btnMinimize.Visible = true;
btnClose.BringToFront();
btnMaximize.BringToFront();
btnMinimize.BringToFront();
}
Please note that compiling showed a serious exception (not shown in errors tab) that said that Top-level control cannot be added to a control.
I had to change this line this.panelDesktopPane.Controls.Add(childForm); to panelDesktopPane.Controls.Add(frmlgn); and I don't really know what consequences it may have.
Thanks again.
I'm new to C# and have created a basic program.
My "Main menu" is setup with 7 tabs along the top.
I have set up CASE so when the user selects the tab it adds that specific submenu (frmXXXmenu) to the controls.
The issue is
Is this correct
Now each form will open each click, thus I keep generating instances of the forms (and I can see my process memory increasing!)
private void tabmain_SelectedIndexChanged(object sender, EventArgs e)
{
string curtab = tabmain.SelectedTab.Name.ToString();
switch (tabmain.SelectedTab.Name)
{
case "tabcollect":
frmcollectmenu frmcollectmenu = new frmcollectmenu();
frmcollectmenu.TopLevel = false;
tabcollect.Controls.Add(frmcollectmenu);
frmcollectmenu.Dock = DockStyle.Fill;
frmcollectmenu.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
frmcollectmenu.Show();
break;
case "tabpack":
frmpackmenu frmpackmenu = new frmpackmenu();
frmpackmenu.TopLevel = false;
tabpack.Controls.Add(frmpackmenu);
frmpackmenu.Dock = DockStyle.Fill;
frmpackmenu.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
frmpackmenu.Show();
break;
}
}
Etc for 7 tabs.
Am I in the right direction and if so, how do I fix this instance situation?
I was thinking either Dispose when no longer active.. or somehow not creating a new instance when one is already open?
Second issue
One of the forms takes about 2 seconds to open (it is showing a chart based on sql). How can i load this page in the tab when i open frmmain() to start the program and then when i open the tab it will be loaded?
I tried putting
frmcollectmenu frmcollectmenu = new frmcollectmenu();
frmcollectmenu.TopLevel = false;
tabcollect.Controls.Add(frmcollectmenu);
frmcollectmenu.Dock = DockStyle.Fill;
frmcollectmenu.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
frmcollectmenu.visible = false;
frmcollectmenu.Show();
(added the visible part) then i made it visible again when selecting the tab, but it seems it still takes a few seconds and doesnt really work right.
If you want only one instance of your forms opened then you should check the Application.OpenForms collection if one of your forms is already referenced there by the system
case "tabcollect":
frmcollectmenu f = Application.OpenForms["frmcollectmenu"];
if(f == null)
{
frmcollectmenu frmcollectmenu = new frmcollectmenu();
frmcollectmenu.TopLevel = false;
tabcollect.Controls.Add(frmcollectmenu);
frmcollectmenu.Dock = DockStyle.Fill;
frmcollectmenu.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
frmcollectmenu.Show();
}
else
f.Show();
break;
Notice that this works if you leave the name property of the form to its default name (same as the class name of the form)
You can also try to isolate the logic about your open forms in separate methods for each form and implement a dictionary where you store the tab key and the action to execute when the user select that particular tab
// At form global level....
Dictionary<string, Action> tabActions = new Dictionary<string, Action>()
{
{ "tabCollect", runCollect },
{ ..... },
so on for other tabs
}
// The logic for a particular form (the same are needed for others forms)
private static void runCollect()
{
frmcollectmenu f = Application.OpenForms["frmcollectmenu"];
if(f == null)
{
frmcollectmenu frmcollectmenu = new frmcollectmenu();
frmcollectmenu.TopLevel = false;
tabcollect.Controls.Add(frmcollectmenu);
frmcollectmenu.Dock = DockStyle.Fill;
frmcollectmenu.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
frmcollectmenu.Show();
}
else
f.Show();
}
Now you can remove the switch in your SelectedIndexChanged method and just select the appropriate action from the dictionary statically build at startup time of your main form
private void tabmain_SelectedIndexChanged(object sender, EventArgs e)
{
string curtab = tabmain.SelectedTab.Name.ToString();
if(tabActions.ContainsKey(curTab))
tabActions[curTab].Invoke();
}
I'm trying to execute code in a SizeChangedEventHandler but the following is not working:
[TestMethod]
public void TestSizeChanged()
{
var panel = new System.Windows.Controls.StackPanel();
bool handled = false;
panel.SizeChanged += (o, e) =>
{
handled = true; // how to get this to be executed
};
panel.Width = 100; // naive attempt to change size!
Assert.IsTrue(handled);
}
I originally tried to use the RaiseEvent method but I was not been able to supply it with the correct xxxEventArgs type, due to not knowing the constructor arguments and the object browser is not helping:
panel.RaiseEvent(new System.Windows.SizeChangedEventArgs()) // does not compile
Obviously, the above test serves no purpose but I'm after correct way of getting the event to fire in a unit-tested environment.
It's very strange that the SizeChanged event doesn't fire with your code, it appears to be correct. Maybe the StackPanel doesn't exists in the visual tree because it's not really shown on the screen, so the event is never fired.
Try to show a real window with a StackPanel on the screen, and programmatically change his width or height.
[TestMethod]
public void TestSizeChanged()
{
Window wnd = new Window();
wnd.Content = new System.Windows.Controls.StackPanel();
bool handled = false;
wnd.SizeChanged += (o, e) =>
{
handled = true; // how to get this to be executed
};
wnd.Show();
wnd.Width = 100; // naive attempt to change size!
Assert.IsTrue(handled);
}
You can't use the RaiseEvent method, because SizeChanged is not a RoutedEvent.
Using the below reflection you can succeed:
//panel =>System.Windows.Controls.Panel instance..
SizeChangedInfo sifo = new SizeChangedInfo(panel, new Size(0, 0), true, true);
SizeChangedEventArgs ea = typeof(System.Windows.SizeChangedEventArgs).GetConstructors(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).FirstOrDefault().Invoke(new object[] {(panel as FrameworkElement),sifo }) as SizeChangedEventArgs;
ea.RoutedEvent = Panel.SizeChangedEvent;
panel.RaiseEvent(ea);
I create a global hot key to show a window by PInvoking RegisterHotKey(). But to do this I need that window's HWND, which doesn't exist until the window is loaded, that means shown for the first time. But I don't want to show the window before I can set the hot key. Is there a way to create a HWND for that window that is invisible to the user?
If you are targeting .NET 4.0 you can make use of the new EnsureHandle method available on the WindowInteropHelper:
public void InitHwnd()
{
var helper = new WindowInteropHelper(this);
helper.EnsureHandle();
}
(thanks to Thomas Levesque for pointing this out.)
If you are targeting an older version of the .NET Framework, the easiest way is to show the window to get to the HWND while setting a few properties to make sure that the window is invisible and doesn't steal focus:
var window = new Window() //make sure the window is invisible
{
Width = 0,
Height = 0,
WindowStyle = WindowStyle.None,
ShowInTaskbar = false,
ShowActivated = false
};
window.Show();
Once you want to show the actual window you can then set the Content, the size and change the style back to a normal window.
You can also change the window into a so called message-only window. As this window type does not support graphical elements it will never be shown. Basically it comes down to calling:
SetParent(hwnd, (IntPtr)HWND_MESSAGE);
Either create a dedicated message window which will always be hidden, or use the real GUI window and change it back to a normal window when you want to display it. See the code below for a more complete example.
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hwnd, IntPtr hwndNewParent);
private const int HWND_MESSAGE = -3;
private IntPtr hwnd;
private IntPtr oldParent;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
if (hwndSource != null)
{
hwnd = hwndSource.Handle;
oldParent = SetParent(hwnd, (IntPtr)HWND_MESSAGE);
Visibility = Visibility.Hidden;
}
}
private void OpenWindowMenuItem_Click(object sender, RoutedEventArgs e)
{
SetParent(hwnd, oldParent);
Show();
Activate();
}
For me the solution of setting the width, height to zero and style to none didn't work out, as it still showed a tiny window, with an annoying shadow of what seems to be the border around a 0x0 window (tested on Windows 7). Therefore I'm providing this alternative option.
This is a dirty hack, but it should work, and doesn't have the downsides of changing the opacity :
set the WindowStartupLocation to Manual
set the Top and Left properties to somewhere outside the screen
set ShowInTaskbar to false so that the user doesn't realize there is a new window
Show and Hide the window
You should now be able to retrieve the HWND
EDIT: another option, probably better : set ShowInTaskBar to false and WindowState to Minimized, then show it : it won't be visible at all
I had already posted an answer to that question, but I just found a better solution.
If you just need to make sure that the HWND is created, without actually showing the window, you can do this:
public void InitHwnd()
{
var helper = new WindowInteropHelper(this);
helper.EnsureHandle();
}
(actually the EnsureHandle method wasn't available when the question was posted, it was introduced in .NET 4.0)
I've never tried to do what you are doing, but if you need to show the Window to get the HWND, but don't want to show it, set the Window Opacity to 0. This will also prevent any hit testing from occurring. Then you could have a public method on the Window to change the Opacity to 100 when you want to make it visible.
I know absolutely nothing about WPF, but could you create a message only window using other means (PInvoke for example) to receive the WM_HOTKEY message? If yes, then once you receive the WM_HOTKEY, you could launch the WPF window from there.
I've noticed that the last thing that happens when the window is being initialized, is the change of WindowState, if it differs from normal. So, you can actually make use of it:
public void InitializeWindow(Window window) {
window.Top = Int32.MinValue;
window.Left = Int32.MinValue;
window.Width = 0;
window.Height = 0;
window.ShowActivated = false;
window.ShowInTaskbar = false;
window.Opacity = 0;
window.StateChanged += OnBackgroundStateChanged;
window.WindowStyle = WindowStyle.None;
}
public void ShowWindow(Window window) {
window.Show();
window.WindowState = WindowState.Maximized;
}
protected bool isStateChangeFirst = true;
protected void OnBackgroundStateChanged(object sender, EventArgs e) {
if (isStateChangeFirst) {
isStateChangeFirst = false;
window.Top = 300;
window.Left = 200;
window.Width = 760;
window.Height = 400;
window.WindowState = WindowState.Normal;
window.ShowInTaskbar = true;
window.Opacity = 1;
window.Activate();
}
}
That works fair enough for me. And it does not require working with any handles and stuff, and, more importantly, does not require to have a custom class for a window. Which is great for dynamically loaded XAML. And it is also a great way if you are making a fullscreen app. You do not even need to change its state back to normal or set proper width and height. Just go with
protected bool isStateChangeFirst = true;
protected void OnBackgroundStateChanged(object sender, EventArgs e) {
if (isStateChangeFirst) {
isStateChangeFirst = false;
window.ShowInTaskbar = true;
window.Opacity = 1;
window.Activate();
}
}
And you're done.
And even if I am wrong in my assumption that change of state is last thing done when window is being loaded, you can still change to any other event, it does not really matter.
The WindowInteropHelper class should allow you to get the HWND for the WPF window.
MyWindow win = new MyWindow();
WindowInteropHelper helper = new WindowInteropHelper(win);
IntPtr hwnd = helper.Handle;
MSDN Documentation
Another option in a similar vein to setting the opacity to 0, is to set the size to 0 and set the position to be off the screen. This won't require the AllowsTransparency = True.
Also remember that once you have shown it once you can then hide it and still get the hwnd.
Make the size of the window 0 x 0 px, put ShowInTaskBar to false, show it, then resize it when needed.
I've created extension method for showing invisible window, next Show calls will behave OK.
public static class WindowHelper
{
public static void ShowInvisible(this Window window)
{
// saving original settings
bool needToShowInTaskbar = window.ShowInTaskbar;
WindowState initialWindowState = window.WindowState;
// making window invisible
window.ShowInTaskbar = false;
window.WindowState = WindowState.Minimized;
// showing and hiding window
window.Show();
window.Hide();
// restoring original settings
window.ShowInTaskbar = needToShowInTaskbar;
window.WindowState = initialWindowState;
}
}
Start Wpf Window in Hidden mode:
WpfWindow w = new WpfWindow() { Visibility = Visibility.Hidden };
Start Wpf Window in Visible mode:
WpfWindow w = new WpfWindow();
w.Show();
I am working on UI where i have some controls like textbox, button, radiobutton and more.
On some specific business workflows i have to play with my controls and set their state as enable/disable or set visibility of them.
When i do this my whole UI code behind (xaml.cs) scattered with controls enabling and disabling.
i want to write a single method which is completely responsible for handling the state of my UI controls.
Currently what i have done is - making a single function which take the operation as parameter and then based on operation i am playing with my controls.
For example:
private const string ADD_OPERATION = "Add";
private const string MODIFY_OPERATION = "Modify";
private const string DELETE_OPERATION = "Delete";
-------------------------------------------------------
/// <summary>
/// Method for disabling controls present on current screen
/// based on various operations like add, delete, edit identity
/// </summary>
private void EnableOrDisableIdentityControl(string operation)
{
switch (operation)
{
case ADD_OPERATION:
this.EditIdentificationGroupBox.IsEnabled = true;
this.AddUpdateIdentificationButton.IsEnabled = true;
this.RelatedEntitiesGridFooter.IsAddButtonEnabled = false;
this.RelatedEntitiesGridFooter.IsEditButtonEnabled = false;
this.RelatedEntitiesGridFooter.IsDeleteButtonEnabled = false;
this.IdentificationDataGrid.IsEnabled = false;
break;
case MODIFY_OPERATION:
this.EditIdentificationGroupBox.IsEnabled = true;
// Disable identity grid
this.IdentificationDataGrid.IsEnabled = false;
this.RelatedEntitiesGridFooter.IsAddButtonEnabled = false;
this.RelatedEntitiesGridFooter.IsEditButtonEnabled = false;
this.RelatedEntitiesGridFooter.IsDeleteButtonEnabled = false;
this.EditIdentificationGroupBox.Focus();
break;
case DELETE_OPERATION:
this.EditIdentificationGroupBox.IsEnabled = false;
break;
default:
this.EditIdentificationGroupBox.IsEnabled = false;
if (this.IdentificationDataGrid.Items.Count == 0)
{
this.RelatedEntitiesGridFooter.IsAddButtonEnabled = true;
this.RelatedEntitiesGridFooter.IsEditButtonEnabled = false;
this.RelatedEntitiesGridFooter.IsDeleteButtonEnabled = false;
}
else
{
this.RelatedEntitiesGridFooter.IsAddButtonEnabled = true;
this.RelatedEntitiesGridFooter.IsEditButtonEnabled = true;
this.RelatedEntitiesGridFooter.IsDeleteButtonEnabled = true;
}
break;
}
}
I want to know is there is any better approach for effectively handling controls state in code behind?
I am not sure using winform way of code-behind handling is right way to go. WPF was specifically designed to get rid of tight coupling of view with code-behind. I would suggest to use MVVM pattern (Model-View-ViewModel) intended for WPF which would solve all these issues you mentioned.