I'd like to know what's the best way (read most elegant) to have a single instance of a given Window per application in WPF.
我想知道在WPF中为每个应用程序提供给定Window的单个实例的最佳方法(阅读最优雅)是什么。
I'm a newcomer to .NET and WPF and what I came up with looks pretty lame.
我是.NET和WPF的新手,我想出的东西看起来很蹩脚。
private static readonly Object MUTEX = new Object();
private static AboutWindow INSTANCE;
public static AboutWindow GetOrCreate() {
lock (MUTEX) {
if (INSTANCE == null) {
INSTANCE = new AboutWindow();
}
INSTANCE.Show();
return INSTANCE;
}
}
private AboutWindow() {
InitializeComponent();
}
private void AboutWindow_Closed(object sender, EventArgs e) {
// the Closed events are handy for me to update values across
// different windows.
lock (MUTEX) {
INSTANCE = null;
}
}
Thing is... this looks like utter crap. There must be some way to achieve the same goal in a much more elegant way, right?
事情是......这看起来像完全废话。必须有一些方法以更优雅的方式实现同一目标,对吗?
PS: I'm often using the Closed
event to change values in other open windows. For instance I have the SettingsWindow with the "Account" button. When I push that button, the AccountWindow pops up. When I close AcountWindow, I want something in the SettingsWindow to change (a label). Hence the constant creation of windows.
Besides, Close is something you always have to deal with because of the X button on the window frame...
PS:我经常使用Closed事件来更改其他打开窗口中的值。例如,我有SettingsWindow和“帐户”按钮。当我按下该按钮时,会弹出AccountWindow。当我关闭AcountWindow时,我想要在SettingsWindow中更改(标签)。因此不断创建窗口。此外,由于窗框上的X按钮,Close总是需要处理的...
4 个解决方案
#1
5
If you truly need to enforce a single instance of a window, then a static instance (some flavor of what you have) with a factory creation method is certainly a viable option, much like a single DataContext instance when working with a database.
如果你真的需要强制执行窗口的单个实例,那么使用工厂创建方法的静态实例(你所拥有的一些东西)当然是一个可行的选择,就像使用数据库时的单个DataContext实例一样。
You could also write your own WindowManager class, although that seems like overkill, and will essentially be the same thing (except the Factory methods would be in a single class).
您也可以编写自己的WindowManager类,虽然这看起来有些过分,但基本上是相同的(除了Factory方法将在单个类中)。
However, re-reading your post, I wonder if this is a case of missing the forest for the trees. Your mentioning of your SettingsWindow, which in turn calls AccountWindow, makes me think that you should simply be using ShowDialog(). This opens a window modally, meaning that there can be no interaction with the calling window (or any other window in your application). You simply set a property in that dialog, set the DialogResult to true when the OK button is pressed, and read that property in the parent window.
然而,重新阅读你的帖子,我想知道这是否是一个错过森林树木的情况。你提到你的SettingsWindow,它反过来调用AccountWindow,让我觉得你应该只使用ShowDialog()。这会以模态方式打开一个窗口,这意味着不能与调用窗口(或应用程序中的任何其他窗口)进行交互。您只需在该对话框中设置属性,在按下确定按钮时将DialogResult设置为true,并在父窗口中读取该属性。
Basically, you just use the ShowDialog like this. I am leaving out a lot of the implementation details, as far as binding vs. hard-coding to controls. Those details aren't as important as just seeing how ShowDialog works.
基本上,你只需使用这样的ShowDialog。关于控件的绑定与硬编码,我遗漏了很多实现细节。这些细节并不像看到ShowDialog如何工作那么重要。
For simplicity, assume that you have a class called MyAppOptions that, well, reflect the options of your application. I will leave off most of the implementation details of this for simplicity, but it would likely implement INotifyPropertyChanged, have methods and fields and properties, etc.
为简单起见,假设您有一个名为MyAppOptions的类,它反映了您的应用程序的选项。为简单起见,我将省略大部分实现细节,但它可能实现INotifyPropertyChanged,具有方法,字段和属性等。
public class MyAppOptions
{
public MyAppOptions()
{
}
public Boolean MyBooleanOption
{
get;
set;
}
public String MyStringOption
{
get;
set;
}
}
Then, let's make this simple, and assume that you want to show an Options dialog when you press a button on some window. Furthermore, I will assume that there are variables that have been set with your options, which were loaded at startup.
然后,让我们简单一点,并假设您想要在某个窗口上按下按钮时显示“选项”对话框。此外,我将假设已经使用您的选项设置了变量,这些变量在启动时加载。
void btnOptions_Click(object sender, RoutedEventArgs e)
{
MyAppOptions options = new MyAppOptions();
options.MyBooleanOption = mSomeBoolean;
options.MyStringOption = mSomeString;
OptionsDialog optionsDialog = new optionsDialog(options);
if (optionsDialog.ShowDialog() == true)
{
// Assume this function saves the options to storage
// and updates the application (binding) appropriately
SetAndSaveOptions(optionsDialog.AppOptions);
}
}
Now assume that the OptionsDialog is a window you've created in your project, and it has a CheckBox on it related to MyBooleanOption and a TextBox for MyStringOption. It also has an Ok button and a Cancel button. The code-behind will likely use Binding, but for now we'll hard code the values.
现在假设OptionsDialog是你在项目中创建的一个窗口,它上面有一个与MyBooleanOption相关的CheckBox和一个用于MyStringOption的TextBox。它还有一个Ok按钮和一个Cancel按钮。代码隐藏可能会使用Binding,但是现在我们将对这些值进行硬编码。
public class OptionsDialog : Window
{
public OptionsDialog(MyAppOptions options)
{
chkBooleanOption.IsChecked = options.SomeBooleanOption;
txtStringOption.Text = options.SomeStringOption;
btnOK.Click += new RoutedEventHandler(btnOK_Click);
btnCancel.Click += new RoutedEventHandler(btnCancel_Click);
}
public MyAppOptions AppOptions
{
get;
set;
}
void btnOK_Click(object sender, RoutedEventArgs e)
{
this.AppOptions.SomeBooleanOption = (Boolean) chkBooleanOption.IsChecked;
this.AppOptions.SomeStringOption = txtStringOption.Text;
// this is the key step - it will close the dialog and return
// true to ShowDialog
this.DialogResult = true;
}
void btnClose_Click(object sender, RoutedEventArgs e)
{
// this will close the dialog and return false to ShowDialog
// Note that pressing the X button will also return false to ShowDialog
this.DialogResult = false;
}
}
This is a pretty basic example as far as implementation details. Search online for ShowDialog for more details. The important keys to remember are:
就实现细节而言,这是一个非常基本的例子。在线搜索ShowDialog以获取更多详细信息。要记住的重要关键是:
- ShowDialog opens a window modally, meaning it is the only window in your application that can be interacted with.
- ShowDialog以模态方式打开一个窗口,这意味着它是应用程序中唯一可以与之交互的窗口。
- Setting DialogResult to true will close the dialog, which can be checked for from the calling parent.
- 将DialogResult设置为true将关闭对话框,可以从调用父对象中检查该对话框。
- Setting DialogResult to false will also close the dialog, in which case you skip updating the values in the calling window.
- 将DialogResult设置为false也将关闭对话框,在这种情况下,您将跳过更新调用窗口中的值。
- Pressing the X button on the window automatically sets the DialogResult to false
- 按下窗口上的X按钮会自动将DialogResult设置为false
- You can have public properties in the dialog window that can be set before doing the ShowDialog, and can get values from after the dialog disappears. It will be available while the dialog is still in scope.
- 您可以在对话框窗口中拥有可以在执行ShowDialog之前设置的公共属性,并且可以在对话框消失后从中获取值。当对话框仍在范围内时,它将可用。
#2
13
there are probably better ways to do this, but here is a relatively simple way.... put a static bool on your window class to flag if its open or not. then, in the load() event set it to true, and on the close event set it to false. Then, in the code that opens the window, check the flag.
有可能更好的方法来做到这一点,但这是一个相对简单的方法....在你的窗口类上放置一个静态bool来标记它是否打开。然后,在load()事件中将其设置为true,并在close事件上将其设置为false。然后,在打开窗口的代码中,检查标志。
here is some pseudo-code to give you an idea...
这里有一些伪代码可以给你一个想法......
public class AboutWindow
{
public static bool IsOpen {get;private set;}
onLoadEvent(....)
{
IsOpen = true;
}
onUnloadEvent(...)
{
IsOpen = false;
}
}
public void OpenAbout()
{
if ( AboutWindow.IsOpen ) return;
AboutWindow win = new AboutWindow();
win.Show();
}
#3
1
The following extends on the above solution to reshow the window if it is already open. In this case it is a help window.
以下扩展上述解决方案,以重新显示窗口,如果它已经打开。在这种情况下,它是一个帮助窗口。
///<summary>
/// Show help from the resources for a particular control by contextGUID
///</summary>
///<param name="contextGUID"></param>
private void ShowApplicationHelp(string contextGUID = "1")
{
if (HelpWin != null)
{
if (HelpWin.IsOpen)
{
HelpWin.BringToFront();
return;
}
}
HelpWin = new MigratorHelpWindow();
HelpWin.Owner = Application.Current.MainWindow;
HelpWin.ResizeMode = ResizeMode.CanResizeWithGrip;
HelpWin.Icon = new Image()
{
Source =
new BitmapImage(
new Uri(
"pack://application:,,,/ResourceLibrary;component/Resources/Images/Menu/Help.png",
UriKind.RelativeOrAbsolute))
};
HelpWin.Show();
HelpWin.BringToFront();
}
This code is all in a viewmodel (MVVM) associated with the window. It is called by an ICommand hooked to a button on the window (naturally, it shows a question mark!!) The following property is involved (in this case it is a Telerik RadWindow but it can be any window object, and you can probably also just store the window handle but using this property permits manipulation of the object more smoothly e.g. HelpWin.BringToFront() as in the above example...
此代码全部位于与窗口关联的视图模型(MVVM)中。它由一个挂在窗口上的按钮上的ICommand调用(当然,它显示一个问号!!)涉及以下属性(在这种情况下它是一个Telerik RadWindow,但它可以是任何窗口对象,你可能也只是存储窗口句柄,但使用此属性可以更顺利地操作对象,例如HelpWin.BringToFront(),如上例所示...
...
...
private Telerik.Windows.Controls.RadWindow **HelpWin**
{
get;
set;
}
...
...
In the window itself (WPF window)
在窗口本身(WPF窗口)
///<summary>
/// Flag to indicate the window is open - use to prevent opening this particular help window multiple times...
///</summary>
public static bool IsOpen { get; private set; }
...
...
...
private void HelpWindowLoaded(object sender, RoutedEventArgs e)
{
IsOpen = true;
}
private void HelpWindowUnloaded(object sender, RoutedEventArgs e)
{
IsOpen = false;
}
and in the view Xaml ... ...
并在视图中Xaml ......
DataContext="{Binding Path=OnlineHelpViewModelStatic,Source={StaticResource Locator}}"
RestoreMinimizedLocation="True"
**Loaded="HelpWindowLoaded" Unloaded="HelpWindowUnloaded"** >
#4
0
How about using a Singleton?
如何使用Singleton?
public class MyWindow : Window {
private static MyWindow instance;
public static MyWindow Instance
{
get
{
if (instance == null)
{
instance = new MyWindow();
}
return instance;
}
}
}
Then just use
然后就用吧
MyWindow.Instance.Show() and MyWindow.Instance.Hide()
#1
5
If you truly need to enforce a single instance of a window, then a static instance (some flavor of what you have) with a factory creation method is certainly a viable option, much like a single DataContext instance when working with a database.
如果你真的需要强制执行窗口的单个实例,那么使用工厂创建方法的静态实例(你所拥有的一些东西)当然是一个可行的选择,就像使用数据库时的单个DataContext实例一样。
You could also write your own WindowManager class, although that seems like overkill, and will essentially be the same thing (except the Factory methods would be in a single class).
您也可以编写自己的WindowManager类,虽然这看起来有些过分,但基本上是相同的(除了Factory方法将在单个类中)。
However, re-reading your post, I wonder if this is a case of missing the forest for the trees. Your mentioning of your SettingsWindow, which in turn calls AccountWindow, makes me think that you should simply be using ShowDialog(). This opens a window modally, meaning that there can be no interaction with the calling window (or any other window in your application). You simply set a property in that dialog, set the DialogResult to true when the OK button is pressed, and read that property in the parent window.
然而,重新阅读你的帖子,我想知道这是否是一个错过森林树木的情况。你提到你的SettingsWindow,它反过来调用AccountWindow,让我觉得你应该只使用ShowDialog()。这会以模态方式打开一个窗口,这意味着不能与调用窗口(或应用程序中的任何其他窗口)进行交互。您只需在该对话框中设置属性,在按下确定按钮时将DialogResult设置为true,并在父窗口中读取该属性。
Basically, you just use the ShowDialog like this. I am leaving out a lot of the implementation details, as far as binding vs. hard-coding to controls. Those details aren't as important as just seeing how ShowDialog works.
基本上,你只需使用这样的ShowDialog。关于控件的绑定与硬编码,我遗漏了很多实现细节。这些细节并不像看到ShowDialog如何工作那么重要。
For simplicity, assume that you have a class called MyAppOptions that, well, reflect the options of your application. I will leave off most of the implementation details of this for simplicity, but it would likely implement INotifyPropertyChanged, have methods and fields and properties, etc.
为简单起见,假设您有一个名为MyAppOptions的类,它反映了您的应用程序的选项。为简单起见,我将省略大部分实现细节,但它可能实现INotifyPropertyChanged,具有方法,字段和属性等。
public class MyAppOptions
{
public MyAppOptions()
{
}
public Boolean MyBooleanOption
{
get;
set;
}
public String MyStringOption
{
get;
set;
}
}
Then, let's make this simple, and assume that you want to show an Options dialog when you press a button on some window. Furthermore, I will assume that there are variables that have been set with your options, which were loaded at startup.
然后,让我们简单一点,并假设您想要在某个窗口上按下按钮时显示“选项”对话框。此外,我将假设已经使用您的选项设置了变量,这些变量在启动时加载。
void btnOptions_Click(object sender, RoutedEventArgs e)
{
MyAppOptions options = new MyAppOptions();
options.MyBooleanOption = mSomeBoolean;
options.MyStringOption = mSomeString;
OptionsDialog optionsDialog = new optionsDialog(options);
if (optionsDialog.ShowDialog() == true)
{
// Assume this function saves the options to storage
// and updates the application (binding) appropriately
SetAndSaveOptions(optionsDialog.AppOptions);
}
}
Now assume that the OptionsDialog is a window you've created in your project, and it has a CheckBox on it related to MyBooleanOption and a TextBox for MyStringOption. It also has an Ok button and a Cancel button. The code-behind will likely use Binding, but for now we'll hard code the values.
现在假设OptionsDialog是你在项目中创建的一个窗口,它上面有一个与MyBooleanOption相关的CheckBox和一个用于MyStringOption的TextBox。它还有一个Ok按钮和一个Cancel按钮。代码隐藏可能会使用Binding,但是现在我们将对这些值进行硬编码。
public class OptionsDialog : Window
{
public OptionsDialog(MyAppOptions options)
{
chkBooleanOption.IsChecked = options.SomeBooleanOption;
txtStringOption.Text = options.SomeStringOption;
btnOK.Click += new RoutedEventHandler(btnOK_Click);
btnCancel.Click += new RoutedEventHandler(btnCancel_Click);
}
public MyAppOptions AppOptions
{
get;
set;
}
void btnOK_Click(object sender, RoutedEventArgs e)
{
this.AppOptions.SomeBooleanOption = (Boolean) chkBooleanOption.IsChecked;
this.AppOptions.SomeStringOption = txtStringOption.Text;
// this is the key step - it will close the dialog and return
// true to ShowDialog
this.DialogResult = true;
}
void btnClose_Click(object sender, RoutedEventArgs e)
{
// this will close the dialog and return false to ShowDialog
// Note that pressing the X button will also return false to ShowDialog
this.DialogResult = false;
}
}
This is a pretty basic example as far as implementation details. Search online for ShowDialog for more details. The important keys to remember are:
就实现细节而言,这是一个非常基本的例子。在线搜索ShowDialog以获取更多详细信息。要记住的重要关键是:
- ShowDialog opens a window modally, meaning it is the only window in your application that can be interacted with.
- ShowDialog以模态方式打开一个窗口,这意味着它是应用程序中唯一可以与之交互的窗口。
- Setting DialogResult to true will close the dialog, which can be checked for from the calling parent.
- 将DialogResult设置为true将关闭对话框,可以从调用父对象中检查该对话框。
- Setting DialogResult to false will also close the dialog, in which case you skip updating the values in the calling window.
- 将DialogResult设置为false也将关闭对话框,在这种情况下,您将跳过更新调用窗口中的值。
- Pressing the X button on the window automatically sets the DialogResult to false
- 按下窗口上的X按钮会自动将DialogResult设置为false
- You can have public properties in the dialog window that can be set before doing the ShowDialog, and can get values from after the dialog disappears. It will be available while the dialog is still in scope.
- 您可以在对话框窗口中拥有可以在执行ShowDialog之前设置的公共属性,并且可以在对话框消失后从中获取值。当对话框仍在范围内时,它将可用。
#2
13
there are probably better ways to do this, but here is a relatively simple way.... put a static bool on your window class to flag if its open or not. then, in the load() event set it to true, and on the close event set it to false. Then, in the code that opens the window, check the flag.
有可能更好的方法来做到这一点,但这是一个相对简单的方法....在你的窗口类上放置一个静态bool来标记它是否打开。然后,在load()事件中将其设置为true,并在close事件上将其设置为false。然后,在打开窗口的代码中,检查标志。
here is some pseudo-code to give you an idea...
这里有一些伪代码可以给你一个想法......
public class AboutWindow
{
public static bool IsOpen {get;private set;}
onLoadEvent(....)
{
IsOpen = true;
}
onUnloadEvent(...)
{
IsOpen = false;
}
}
public void OpenAbout()
{
if ( AboutWindow.IsOpen ) return;
AboutWindow win = new AboutWindow();
win.Show();
}
#3
1
The following extends on the above solution to reshow the window if it is already open. In this case it is a help window.
以下扩展上述解决方案,以重新显示窗口,如果它已经打开。在这种情况下,它是一个帮助窗口。
///<summary>
/// Show help from the resources for a particular control by contextGUID
///</summary>
///<param name="contextGUID"></param>
private void ShowApplicationHelp(string contextGUID = "1")
{
if (HelpWin != null)
{
if (HelpWin.IsOpen)
{
HelpWin.BringToFront();
return;
}
}
HelpWin = new MigratorHelpWindow();
HelpWin.Owner = Application.Current.MainWindow;
HelpWin.ResizeMode = ResizeMode.CanResizeWithGrip;
HelpWin.Icon = new Image()
{
Source =
new BitmapImage(
new Uri(
"pack://application:,,,/ResourceLibrary;component/Resources/Images/Menu/Help.png",
UriKind.RelativeOrAbsolute))
};
HelpWin.Show();
HelpWin.BringToFront();
}
This code is all in a viewmodel (MVVM) associated with the window. It is called by an ICommand hooked to a button on the window (naturally, it shows a question mark!!) The following property is involved (in this case it is a Telerik RadWindow but it can be any window object, and you can probably also just store the window handle but using this property permits manipulation of the object more smoothly e.g. HelpWin.BringToFront() as in the above example...
此代码全部位于与窗口关联的视图模型(MVVM)中。它由一个挂在窗口上的按钮上的ICommand调用(当然,它显示一个问号!!)涉及以下属性(在这种情况下它是一个Telerik RadWindow,但它可以是任何窗口对象,你可能也只是存储窗口句柄,但使用此属性可以更顺利地操作对象,例如HelpWin.BringToFront(),如上例所示...
...
...
private Telerik.Windows.Controls.RadWindow **HelpWin**
{
get;
set;
}
...
...
In the window itself (WPF window)
在窗口本身(WPF窗口)
///<summary>
/// Flag to indicate the window is open - use to prevent opening this particular help window multiple times...
///</summary>
public static bool IsOpen { get; private set; }
...
...
...
private void HelpWindowLoaded(object sender, RoutedEventArgs e)
{
IsOpen = true;
}
private void HelpWindowUnloaded(object sender, RoutedEventArgs e)
{
IsOpen = false;
}
and in the view Xaml ... ...
并在视图中Xaml ......
DataContext="{Binding Path=OnlineHelpViewModelStatic,Source={StaticResource Locator}}"
RestoreMinimizedLocation="True"
**Loaded="HelpWindowLoaded" Unloaded="HelpWindowUnloaded"** >
#4
0
How about using a Singleton?
如何使用Singleton?
public class MyWindow : Window {
private static MyWindow instance;
public static MyWindow Instance
{
get
{
if (instance == null)
{
instance = new MyWindow();
}
return instance;
}
}
}
Then just use
然后就用吧
MyWindow.Instance.Show() and MyWindow.Instance.Hide()