In WPF you get to call ShowDialog on a window exactly once. After that it is done for.
Seems kind of lame to me, but those are the rules. If you call ShowDialog again you get this exception:
Cannot set Visibility or call Show, ShowDialog, or WindowInteropHelper.EnsureHandle after a Window has closed
What I want to know is: How can I take a Window (or UserControl really) and check to see if it has had ShowDialog called (so I know to new up a different one before calling ShowDialog again).
Something like this:
public void ShowListOfClients()
{
// | This is the method I want to write
// V
RefreshViewIfNeeded(_myWindowOrUserControlThatShowsAList);
FillWindowWithBusinessData(_myWindowOrUserControlThatShowsAList);
_myWindowOrUserControlThatShowsAList.ShowDialog();
}
NOTE: Clearly in the above example it would be easier to just create a new WindowOrUserControlThatShowsAList every time I enter the method. But please consider the question more that the dumbed down example.
This isn't exclusive to ShowDialog(), Show() does it too. And no, there is no IsDisposed property to check. IsLoaded is only half a solution, it will be false for the 1st invocation as well.
First approach is to just make a dialog that can be re-shown:
public bool CloseAllowed { get; set; }
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
if (!CloseAllowed) {
this.Visibility = System.Windows.Visibility.Hidden;
e.Cancel = true;
}
}
The next one is to explicitly keep track of the health of the object reference:
private Window1 win = new Window1(); // say
private void button1_Click(object sender, RoutedEventArgs e) {
if (win == null) {
win = new Window1();
win.Closing += delegate { win = null; };
}
win.ShowDialog();
}
Well the dirty way to do it would be to catch the exception.
The clean way to do it would be to show a window with ShowDialog, and destroy (lose reference to, etc) the window when the function returns. The view should not be tightly coupled with the models (you are using MVVM right?) so creating new visual objects for each client view should not be an issue.
Easy way to deal with this problem without messing up with the Closing event :
public partial class MainWindow
{
private SomeCustomWindow _someCustomWindow;
public MainWindow()
{
InitializeComponent();
}
private void OnOpenCustomWindowButtonClick(object sender, RoutedEventArgs e)
{
if (_someCustomWindow != null)
_someCustomWindow.Close();
_someCustomWindow = new SomeCustomWindow();
_someCustomWindow.ShowDialog();
}
private void OnWindowClosing(object sender, CancelEventArgs e)
{
if (_someCustomWindow!= null)
_someCustomWindow.Close();
}
}
Related
I have implemented a Singleton userControl in order to interact with another classes during the application is running. It works well in background but I want to show (set visibility = true) designed userControl when clicking on a button! When Application is running and I click the button it shows nothing in the designed form. here is the code that I set the visibility to true in Form.cs file:
private void ButtonSearch_Click(object sender, EventArgs e)
{
panelYellow.Height = buttonSearch.Height;
panelYellow.Top = buttonSearch.Top;
searchPanel.getInstance().Visible = true;
}
And here is part of my singleton userControl class definition:
public partial class searchPanel : UserControl
{
private searchPanel()
{
InitializeComponent();
}
private static searchPanel sp = null;
public static searchPanel getInstance()
{
if (sp == null)
{
sp = new searchPanel();
}
return sp;
}
}
It's been a while since I didn't touch winforms, but from what I can remember you cannot really put a private constructor for a UserControl / form.
But another thing that could work is that you can try adding it dynamically in its parent form by setting it with Parent property. Because from what I see, if you didn't put it in the designer, it will never show up.
Anyway the question would be:
- Why do you want to use a singleton for a userControl ?
You must add your searchPanel to form as a control.
private void ButtonSearch_Click(object sender, EventArgs e)
{
panelYellow.Height = buttonSearch.Height;
panelYellow.Top = buttonSearch.Top;
searchPanel.getInstance().Visible = true;
this.Controls.Add(searchPanel.getInstance()); // add this line
}
Also your implementation of Singleton is not wrong but can be improved.
public searchPanel()
{
InitializeComponent();
// write your Singleton code here
}
Can someone please illustrate for me how to set up a logic like this:
I have a WPF Control. When a button is pressed it does one of the two possible things.
A. It checks if a different WPF Window has been loaded. If it was, it triggers that window's Print method.
B. It checks if a different WPF Window has been loaded. If it was not, it instantiates that window and then triggers its Print method.
I struggle to understand the events system between two WPF Controls/Windows. It's a relatively new thing for me, so I would appreciate if someone walked me through this.
Ps. This is not a homework assignment, but rather a new hobby of mine. If its a totally noob question then just point me to a resource so I can educate myself.
Cheers!
First of all, what is the way by which you will check if new Window opened is what you need it to be ?
You might do this by comparing their Handle or their Type (public class MyWindowWithPrintMethod : Window).
There can be multiple ways of doing this.
I suggest my simple way, focusing on the WPF way, to solve your purpose in easiest way possible.
MyWindowWithPrintMethod obj_MyWindowWithPrintMethod;
private void btnNewWindow_Click(object sender, RoutedEventArgs e)
{
obj_MyWindowWithPrintMethod = new MyWindowWithPrintMethod();
obj_MyWindowWithPrintMethod.Show();
}
private void btnCheckNewWindow_Click(object sender, RoutedEventArgs e)
{
WindowInteropHelper tgtWindow = new WindowInteropHelper(obj_MyWindowWithPrintMethod);
foreach (Window w in Application.Current.Windows)
{
// Compare Handle
WindowInteropHelper wih = new WindowInteropHelper(w);
if (wih.Handle == tgtWindow.Handle)
{
((MyWindowWithPrintMethod)w).Print();
}
// Compare Type
if (w.GetType() == typeof(MyWindowWithPrintMethod))
{
((MyWindowWithPrintMethod)w).Print();
}
}
}
MyWindowWithPrintMethod.cs
public class MyWindowWithPrintMethod : Window
{
public void Print()
{
MessageBox.Show("Print invoked !");
}
}
This answer from this question about events from 2 windows may help:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Child childWindow = new Child();
childWindow.MyEvent += new EventHandler(childWindow_MyEvent);
childWindow.ShowDialog();
}
void childWindow_MyEvent(object sender, EventArgs e)
{
// handle event
MessageBox.Show("Handle");
}
}
Child window
public partial class Child : Window
{
// define event
public event EventHandler MyEvent;
protected void OnMyEvent()
{
if (this.MyEvent != null)
this.MyEvent(this, EventArgs.Empty);
}
public Child()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Child_Loaded);
}
void Child_Loaded(object sender, RoutedEventArgs e)
{
// call event
this.OnMyEvent();
}
}
The above code shows how to set up an event from one window to another. But, you might want to simply call a method in that other window instead. For example:
public void AddNewUser()
{
Window2 window = new Window2();
if (window.ShowDialog() == true)
{
// Update DataGrid
RefreshDataGrid();
}
}
If you are determined to stick with events, then you should read up on WPF routed events.
I have a MainForm and AnotherForm. AnotherForm is accessed via MainForm's menuItem.
AnotherForm has listView. When user clicks on an item it I want to get the string element and pass it to MainForm's textbox, so the element shows there and AnotherForm is closed. So far AnotherForm closes but nothing shows in the textbox in MainForm. Any suggestions?
private void listView1_ItemActivate(object sender, EventArgs e)
{
string input = listView1.SelectedItem[0].ToString();
MainForm mainF = new MainForm(input);// called the constructor
this.Close(); //close this form and pass the input to MainForm
mainF.inputTextBox.Text = input;
mainF.loadThis(input);
}
I assume you have an instance of MainForm already, and that's what creates an instance of AnotherForm.
Inside the event you posted, you're actually creating an entirely new instance of MainForm, never showing it, and then it's destroyed anyway when AnotherForm closes.
The reason you see nothing in the text box is because you're looking at the original instance of MainForm, which you haven't actually changed.
One quickie way of fixing this would be passing a reference to the original MainForm into AnotherForm:
public class AnotherForm
{
private MainForm mainF;
public AnotherForm(MainForm mainF)
{
this.mainF = mainF;
}
...
...
private void listView1_ItemActivate(object sender, EventArgs e)
{
...
mainF.inputTextBox.Text = input;
...
}
}
Note: Instead of having AnotherForm aware of MainForm, you might want to switch it around and create a public property in AnotherForm like this:
public class AnotherForm
{
public InputValue { get; private set; }
private void listView1_ItemActivate(object sender, EventArgs e)
{
...
InputValue = input;
...
}
}
Which you can then access from MainForm when the other form is closed:
private void SomeMethodInMainForm()
{
var newAnotherForm = new AnotherForm();
newAnotherForm.ShowDialog();
var inputValueFromAnotherForm = newAnotherForm.InputValue;
// do something with the input value from "AnotherForm"
}
If your MainForm has already been created you cannot just create another one in order to access it and set properties. You've created two separate MainForms (though the 2nd one is hidden because you never showed it).
It sounds like what you want to do is a modal dialog pattern. Your MainForm is the main window in your application. You want to have a 2nd form pop up when you click on a menu link. This is called a dialog. Then when you close that dialog you want your MainForm to retrieve a value as a returned result of the dialog.
In your MainForm the event handler which handles the menu item click should look something like this:
private void pickSomethingMenuItem_Click(object sender, EventArgs e)
{
using (var picker = new PickerDialog())
{
if (picker.ShowDialog(this) == DialogResult.OK)
{
LoadSomething(picker.SomethingPicked);
}
}
}
Then the following code would be inside your dialog form:
public string SomethingPicked { get; private set; }
private void somethingListView_ItemActivate(object sender, EventArgs e)
{
SomethingPicked = somethingListView.SelectedItem[0].ToString();
DialogResult = DialogResult.OK;
}
Notice how I named all of the objects with meaningful names. Well, except for "Something". It was impossible to tell from your code what you were actually using the dialog to pick. You should always use meaningful names for your objects and variables. Your code is almost completely nonsensical.
And you should almost never make a control on a Form public like you have with your inputTextBox. You should always expose values you want to share as public properties.
On this presented solution, you could do five main things in order to achieve what you want to do, namely:
1) Declare a global object for AnotherForm in MainForm
2) Initiate a FromClosing event handler for AnotherForm in MainForm
3) Make a public property or field in AnotherForm
4) Before closing in AnotherForm you save it the public property mentioned above
5) In the MainForm get the public property from AnotherForm
Here is the code:
MainForm
public partial class MainForm : Form
{
AnotherForm anotherForm; // Declare a global object for AnotherForm
public MainForm()
{
InitializeComponent();
}
private void showToolStripMenuItem_Click(object sender, EventArgs e)
{
anotherForm = new AnotherForm(); // when Menu Item is clicked instantiate the Form
anotherForm.FormClosing += new FormClosingEventHandler(anotherForm_FormClosing); // Add a FormClosing event Handler
anotherForm.ShowDialog();
}
void anotherForm_FormClosing(object sender, FormClosingEventArgs e)
{
inputTextBox.Text = anotherForm.listViewValue; // get the Value from public property in AnotherForm
}
}
AnotherForm
void listView1_ItemActivate(object sender, EventArgs e)
{
listViewValue = listView1.SelectedItems[0].Text; // Get the listViewItem value and save to public property
this.Close(); // Close
}
public String listViewValue { get; set; } // public property to store the ListView value
One thing to note here in comparison to your code I didn't use ToString() in ListView.SelectedItems:
listView1.SelectedItems[0].ToString();
But instead use the Text Property:
listView1.SelectedItems[0].Text;
I am working with windowsFrom in c#. I am trying to call mainfrom method in one of the from in user control.
I have mainfrom like this
namespace Project
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
public void TempCommand()
{
StartTemp();
}
}
}
I have the button click in the user control. When i click that button then it will open another form. I have the code like this in the user control.
private TempCalib _tempCalib = new TempCalib();
private void calibBtn_Click(object sender, EventArgs e)
{
_tempCalib.Show();
}
it will open another from and i have one button in that from. I need to call mainfrom method when i click "Ok" button in this from.
namespace Project
{
public partial class TempCalib : Form
{
public TempCalib()
{
InitializeComponent();
}
private void OkButton_Click(object sender, EventArgs e)
{
// I need to call the mainfrom "TempCommand" method here.
this.Hide();
}
}
}
Can anyone help me how to do this.
Thanks.
Quick answer
Just add a reference to the primary form in your secondary form:
public partial class TempCalib : Form
{
private MainForm _main
public TempCalib(MainForm main) : this()
{
_main = main;
}
/// Other stuffs
}
Then assign value when you construct your secondary form:
private TempCalib _tempCalib;
private void calibBtn_Click(object sender, EventArgs e)
{
if (_tempCalib == null)
_tempCalib = new TempCalib(this);
_tempCalib.Show();
}
If calibBtn_Click isn't inside MainForm (but it's inside a UserControl on it) then you can replace _tempCalib initialization with:
_tempCalib = new TempCalib((MainWindow)FindForm());
You'll be then able to call the primary form:
private void OkButton_Click(object sender, EventArgs e)
{
_main.TempCommand();
this.Hide();
}
Notes: this is just one option, you may create a property to hold MainForm reference (so secondary form can be reused and it'll be more designer friendly) moreover TempCalib is not an UserControl but a Form (pretty raw but for an UserControl you may just check its parent Form and cast it to proper type).
Improvements
Such kind of references are often an alert. Usually UI components shouldn't not be so coupled and a public Form's method to perform something very often is the signal that you have too much logic in your Form. How to improve this?
1. DECOUPLE CONTROLS. Well a first step may be to decouple them a little bit, just add an event in TempCalib and make MainForm its receiver:
public partial class TempCalib : Form
{
public event EventHandler SomethingMustBeDone;
private void OkButton_Click(object sender, EventArgs e)
{
OnSomethingMustBeDone(EventArgs.Empty); / TO DO
this.Hide();
}
}
Then in MainForm:
private TempCalib _tempCalib;
private void calibBtn_Click(object sender, EventArgs e)
{
if (_tempCalib == null)
{
_tempCalib = new TempCalib();
_tempCalib.SomethingMustBeDone += _tempCalib_SomethingMustBeDone;
// In _tempCalib_SomethingMustBeDone you'll invoke proper member
// and possibly hide _tempCalib (remove it from OkButton_Click)
}
_tempCalib.Show();
}
2. DECOUPLE LOGIC FROM CONTROLS. UI changes pretty often, logic not (and when it changes probably isn't in parallel with UI). This is just the first step (now TempCalib isn't aware of who will use it). Next step (to be performed when too much things happen inside your form) is to remove this kind of logic from the form itself. Little example (very raw), keep TempCalib as before (with the event) and change MainForm to be passive:
public partial class MainForm : Form
{
public event EventHandler Calibrate;
protected virtual void OnCalibrate(EventArgs e)
{
// TODO
}
}
Now let's create a class to control the flow and logic:
public class MyTaskController
{
private MainForm _main;
private TempCalib _tempCalib;
public void Start()
{
_main = new MainForm();
_main.Calibrate += OnCalibrationRequested;
_main.Show(); // Or whatever else
}
private void OnCalibrationRequested(object sender, EventArgs e)
{
if (_tempCalib == null)
{
_tempCalib = new TempCalib();
_tempCalib.SomethingMustBeDone += OnSomethingMustBeDone();
}
_tempCalib.Show();
}
private OnSomethingMustBeDone(object sender, EventArgs e)
{
// Perform the task here then hide calibration window
_tempCalib.Hide();
}
}
Yes, you'll need to write much more code but this will decouple logic (what to do as response to an action, for example) from UI itself. When program grows up this will help you to change UI as needed keeping logic unaware of that (and in one well defined place). I don't even mention that this will allow you to use different resources (people) to write logic and UI (or to reuse logic for different UI, WinForms and WPF, for example). Anyway IMO the most obvious and well repaid benefit is...readability: you'll always know where logic is and where UI management is, no search, no confusion, no mistakes.
3. DECOUPLE LOGIC FROM IMPLEMENTATION. Again you have more steps to perform (when needed). Your controller is still aware of concrete types (MainForm and TempCalib). In case you need to select a different form at run-time (for example to have a complex interface and a simplified one or to use dependency injection) then you have to decouple controller using interfaces. Just an example:
public interface IUiWindow
{
void Show();
void Hide();
}
public interface IMainWindow : IUiWindow
{
event EventHandler Calibrate;
}
public interface ICalibrationWindow : IUiWindow
{
event EventHandler SomethingMustBeDone;
}
You could use a custom event that is declared in your UserControl. Then your form needs to handle this event and call the method you want to call. If you let the UserControl access your form, you are hard-linking both with each other which decreases reusability of your UserControl.
For example, in TempCalib:
public delegate void OkClickedHandler(object sender, EventArgs e);
public event OkClickedHandler OkClicked;
private void OkButton_Click(object sender, EventArgs e)
{
// Make sure someone is listening to event
if (OkClicked == null) return;
OkClicked(sender, e);
this.Hide();
}
in your mainform:
private void Mainform_Load(object sender, EventArgs e)
{
_tempCalib.OkClicked += CalibOkClicked;
}
private void CalibOkClicked(Object sender, EventArgs e)
{
StartTemp();
}
You create an event in your usercontrol and subscribe to this in the mainform.
That is the usual way.
Form1 Code:
UserControl1 myusercontrol = new UserControl1();
public void TabClose(Object sender,EventArgs e)
{
int i = 0;
i = tabControl1.SelectedIndex;
tabControl1.TabPages.RemoveAt(i);
}
private void Form1_Load(object sender, EventArgs e)
{
myusercontrol.Dock = DockStyle.Fill;
TabPage myTabPage = new TabPage();
myTabPage.Text = "Student";
myTabPage.Controls.Add(myusercontrol);
tabControl1.TabPages.Add(myTabPage);
myusercontrol.OkClick += TabClose;
}
UserControl1 Code:
public delegate void OkClickedHandler(Object sender, EventArgs e);
public partial class UserControl1 : UserControl
{
public event OkClickedHandler OkClick;
public UserControl1()
{
InitializeComponent();
}
private void button3_Click(object sender, EventArgs e)
{
if (OkClick == null) return;
OkClick(sender, e);
}
}
Try this:
From user control try this:
MainForm form = this.TopLevelControl as MainForm;
form.TempCommand();
I have "formA" and 2 buttons on it (button1 and button2). What I want to do is:
When I click on button1 to call "formB" display text written in label1.
When I click button2 to call the same form ("formB") hide label1 and display label2.
The problem is that I don't know how to check what button is clicked on "formA".
Edit: Thanks very much folks for the quick answer. Problem is solved!
This is where events come in handy:
public class FormA
{
private void button1_Click(object sender, EventArgs e)
{
formB.Button1WasClicked();
}
private void button2_Click(object sender, EventArgs e)
{
formB.Button2WasClicked();
}
}
public class FormB
{
public void Button1WasClicked()
{
label2.Visible = false;
label1.Visible = true;
label1.Text = "Button 1 was clicked!";
}
public void Button2WasClicked()
{
label1.Visible = false;
label2.Visible = true;
label2.Text = "Button 2 was clicked!";
}
}
button1 and button2 have their own separate Click event handlers. This way we can differentiate the two when they are clicked.
If you have the same event handler for both buttons (as mentioned in one of the comments), you can identify them with the sender parameter using:
Object.ReferenceEquals(sender, button1);
or
Object.ReferenceEquals(sender, button2);
Then your code would look like this:
private void button_Click(object sender, EventArgs e)
{
if (Object.ReferenceEquals(sender, button1))
{
formB.Button1WasClicked();
}
else
{
formB.Button2WasClicked();
}
}
FormB can't find out, the buttons are a private implementation details of FormA. They might not even be a button, surely you are going to add a menu or a toolbar to FormA some day.
The workaround becomes much simpler if you stop thinking of "calling a form". You never call a form, you create an instance of it. And then you make it visible by calling its Show() method. Lots of things you can do in between those two steps.
Add a public method to FormB. For lack of a better name:
public void MakeLabel2Visible() {
this.label1.Visible = false;
this.label2.Visible = true;
}
Now it becomes simple. Implement button2's Click event handler like this:
private void button2_Click(object sender, EventArgs e) {
var frm = new FormB();
frm.MakeLabel2Visible();
frm.Show();
}
Adding another constructor to a form that lets you initialize it differently is another very common approach. These are just classes, standard programming techniques are appropriate.
Because you are using winforms you can do all this very easily due to the fact that you have a stateful environment.
Assuming a very basic set up with:
event handlers in the code behind of form a
a reference to an instance of form b in form a (or the button click creating such an instance)
a method to use in form b to pass it data
Your code will be something like this:
public partial class FormA : Form
{
private FormB formB;
public FormA()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (formB == null || formB.IsDisposed)
{
formB = new FormB();
}
formB.UpdateLabel("Button A");
formB.Show();
}
private void button2_Click(object sender, EventArgs e)
{
if (formB == null || formB.IsDisposed)
{
formB = new FormB();
}
formB.UpdateLabel("Button B");
formB.Show();
}
}
public partial class FormB : Form
{
public FormB()
{
InitializeComponent();
}
public void UpdateLabel(string message)
{
label1.Text = message;
}
}
Of course, there are lots of improvements to this - using events and alerts more intelligently and refactoring to remove duplication, but this is a basic example of the sort of things you can do.