Memory leak when using SharedResourceDictionary - wpf

if you worked on some larger wpf applications you might be familiar with this. Because ResourceDictionaries are always instantiated, everytime they are found in an XAML we might end up having one resource dictionary multiple times in memory. So the above mentioned solution seems like a very good alternative. In fact for our current project this trick did a lot ... Memory consumption from 800mb down to 44mb, which is a really huge impact. Unfortunately this solution comes at a cost, which i would like to show here, and hopefully find a way to avoid it while still use the SharedResourceDictionary.
I made a small example to visualize the problem with a shared resource dictionary.
Just create a simple WPF Application. Add one resource Xaml
Shared.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="myBrush" Color="Yellow"/>
</ResourceDictionary>
Now add a UserControl. The codebehind is just the default, so i just show the xaml
MyUserControl.xaml
<UserControl x:Class="Leak.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:SharedResourceDictionary="clr-namespace:Articy.SharedResourceDictionary" Height="128" Width="128">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Leak;component/Shared.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Rectangle Fill="{StaticResource myBrush}"/>
</Grid>
</UserControl>
The Window code behind looks something like this
Window1.xaml.cs
// [ ... ]
public Window1()
{
InitializeComponent();
myTabs.ItemsSource = mItems;
}
private ObservableCollection<string> mItems = new ObservableCollection<string>();
private void OnAdd(object aSender, RoutedEventArgs aE)
{
mItems.Add("Test");
}
private void OnRemove(object aSender, RoutedEventArgs aE)
{
mItems.RemoveAt(mItems.Count - 1);
}
And the window xaml like this
Window1.xaml
<Window.Resources>
<DataTemplate x:Key="myTemplate" DataType="{x:Type System:String}">
<Leak:MyUserControl/>
</DataTemplate>
</Window.Resources>
<Grid>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Content="Add" Click="OnAdd"/>
<Button Content="Remove" Click="OnRemove"/>
</StackPanel>
<TabControl x:Name="myTabs" ContentTemplate="{StaticResource myTemplate}">
</TabControl>
</DockPanel>
</Grid>
</Window>
I know the program is not perfect and propably could be made easier but while figuring out a way to show the problem this is what i came up with. Anyway:
Start this and you check the memory consumption, if you have a memory profiler this becomes much easier. Add (with showing it by clicking on the tab) and remove a page and you will see everything works fine. Nothing leaks.
Now in the UserControl.Resources section use the SharedResourceDictionary instead of the ResourceDictionary to include the Shared.xaml. You will see that the MyUserControl will be kept in memory after you removed a page, and the MyUserControl in it.
I figured this happens to everything that is instantiated via XAML like converters, user controls etc. Strangely this won't happen to Custom controls. My guess is, because nothing is really instantiated on custom controls, data templates and so on.
So first how we can avoid that? In our case using SharedResourceDictionary is a must, but the memory leaks makes it impossible to use it productively.
The Leak can be avoided using CustomControls instead of UserControls, which is not always practically. So why are UserControls strong referenced by a ResourceDictionary?
I wonder why nobody experienced this before, like i said in an older question, it seems like we use resource dictionaries and XAML absolutely wrong, otherwise i wonder why they are so inefficent.
I hope somebody can shed some light on this matter.
Thanks in advance
Nico

I'm running into the same issue of needing shared resource directories in a large-ish WPF project. Reading the source article and the comments, I incorporated a couple fixes to the SharedDirectory class as suggested in the comments, which seem to have removed the strong reference (stored in _sourceUri) and also make the designer work correctly. I tested your example and it works, both in the designer and MemProfiler successfully noting no held references. I'd love to know if anyone has improved it further, but this is what i'm going with for now:
public class SharedResourceDictionary : ResourceDictionary
{
/// <summary>
/// Internal cache of loaded dictionaries
/// </summary>
public static Dictionary<Uri, ResourceDictionary> _sharedDictionaries =
new Dictionary<Uri, ResourceDictionary>();
/// <summary>
/// Local member of the source uri
/// </summary>
private Uri _sourceUri;
/// <summary>
/// Gets or sets the uniform resource identifier (URI) to load resources from.
/// </summary>
public new Uri Source
{
get {
if (IsInDesignMode)
return base.Source;
return _sourceUri;
}
set
{
if (IsInDesignMode)
{
try
{
_sourceUri = new Uri(value.OriginalString);
}
catch
{
// do nothing?
}
return;
}
try
{
_sourceUri = new Uri(value.OriginalString);
}
catch
{
// do nothing?
}
if (!_sharedDictionaries.ContainsKey(value))
{
// If the dictionary is not yet loaded, load it by setting
// the source of the base class
base.Source = value;
// add it to the cache
_sharedDictionaries.Add(value, this);
}
else
{
// If the dictionary is already loaded, get it from the cache
MergedDictionaries.Add(_sharedDictionaries[value]);
}
}
}
private static bool IsInDesignMode
{
get
{
return (bool)DependencyPropertyDescriptor.FromProperty(DesignerProperties.IsInDesignModeProperty,
typeof(DependencyObject)).Metadata.DefaultValue;
}
}
}

I am not quite sure if this will solve your issue. But I had similar issues with ResourceDictionary referencing controls and its to do with lazy hydration. Here is a post on it. And this code resolved my issues:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
WalkDictionary(this.Resources);
base.OnStartup(e);
}
private static void WalkDictionary(ResourceDictionary resources)
{
foreach (DictionaryEntry entry in resources)
{
}
foreach (ResourceDictionary rd in resources.MergedDictionaries)
WalkDictionary(rd);
}
}

Related

Impossible to embed WPFToolkit into my executable?

I made a post about this a while back and got no solutions that worked.
No one said it wasn't possible but some people suggested that the error was coming from something else.
So I created a new solution, simple, just added a toolkit control which calls the DLL via the XAML (namespace import) - which I think is the problem.
I added some code to load the DLL's which I have embedded as resources.
my project can be downloaded from here:
https://onedrive.live.com/redir?resid=B293BA834310C42A%21108
for those who don't have time, here's the code:
MainWindow.xaml:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit" x:Class="WpfAppTest1.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<chartingToolkit:ScatterSeries HorizontalAlignment="Left" Margin="63,85,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>
MainWindow.xaml.cs
namespace WpfAppTest1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
and my App.xaml.cs
namespace WpfAppTest1
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public App()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var execAssembly = Assembly.GetExecutingAssembly();
string resourceName = execAssembly.FullName.Split(',').First() + ".Resources." + new AssemblyName(args.Name).Name + ".dll";
Console.WriteLine(resourceName);
using (var stream = execAssembly.GetManifestResourceStream(resourceName))
{
byte[] assemblyData = new byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
}
}
}
The 2 DLL's are:
system.windows.controls.datavisualization.toolkit.dll
WPFToolkit.dll
Both are added as Resources and build action is set to Embedded Resource.
The 2 references are copy local to False.
Looking at the EXE, both files are well built into it.
I added a console writeline to print out the path of the DLL being loaded...
But I still get an error. If I do set the copy local to true, it works.
I am really stuck there and have to use this toolkit and I need the DLL to be part of the EXE. If it's not possible then I'd like to read it from one of you pro's :)
thanks
Steve
edit: Is there no solution to achieve what I need? :(
edit: I still haven't found a solution as of today :/
edit: I wonder if this is even possible.
After quite a bit of effort I seem to have this working.
As your resource for system.windows.controls.datavisualization.toolkit is all in lower case, ensure all of the XAML references to it are also, e.g.:
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=system.windows.controls.datavisualization.toolkit"
This ensures Assembly.GetManifestResourceStream is able to find the resource without any casing issues.
Then change your assembly resolve method to be the following:
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var requestedAssemblyName = new AssemblyName(args.Name).Name;
const string resourcesExtension = ".resources";
if (requestedAssemblyName.EndsWith(resourcesExtension, StringComparison.Ordinal))
{
requestedAssemblyName = requestedAssemblyName.Remove(requestedAssemblyName.Length - resourcesExtension.Length);
}
var resourceName = "WpfAppTest1.Resources." + requestedAssemblyName + ".dll";
var appAssembly = typeof(App).Assembly;
using (var stream = appAssembly.GetManifestResourceStream(resourceName))
{
// Check the resource was found
if (stream == null)
return null;
var assemblyData = new byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
var loadedAssembly = Assembly.Load(assemblyData);
return loadedAssembly;
}
}
The key point to note is the removal of the .resources extension which sometimes comes through when resolving assemblies
This is enough to get the assembly loading from the resources, however there will be no implicit styling applied to the charting controls (I am assuming this is to do with how WPF discovers its themes at runtime).
To work around this problem, I put the following in App.xaml:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/system.windows.controls.datavisualization.toolkit;component/Themes/generic.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
This merges the default chart styles which should then get implicitly applied.
To test it, I used the following content in MainWindow.xaml:
<chartingToolkit:Chart Title="Some Chart" Background="Blue">
<chartingToolkit:Chart.Series>
<chartingToolkit:ScatterSeries Title="Series 1" />
</chartingToolkit:Chart.Series>
</chartingToolkit:Chart>

WPF vs. Silverlight - DataBinding in resources

after some time of silverlight-development I am currently doing some WPF work...
I often used this trick to make my life easier in some of my ValueConverters:
public class MyCovnerterWithDataContext : FrameworkElement, IValueConverter
{
private MyDataContextType Data
{
get { return this.DataContext as MyDataContextType; }
}
....
Now I could access my DataContext in the Converter-Method, which comes handy in lots of situations as you can imagine.
I tried the same trick in WPF and found out, that unfortunately this does not work at all. There is the following error in the debug-output:
"Cannot find element that provides DataContext"
I suppose the resources aren't part of the visual tree in WPF whereas they are in Silverlight.
So - is my little trick possible in WPF as well?
Is my little trick to be considered a dirty hack?
What's your opinion and suggestions?
Regards
Johannes
Update:
as requested some more info - actually a minimal example:
XAML:
<Window x:Class="WpfDataContextInResources.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfDataContextInResources"
x:Name="window"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:TestWrapper x:Key="TestObj" />
</Window.Resources>
<StackPanel>
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding DataContext.Text, Source={StaticResource TestObj}, FallbackValue='FALLBACK'}" />
</StackPanel>
</Window>
the .cs file:
namespace WpfDataContextInResources
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new DataClass()
{
Text = "Hello",
};
}
}
public class TestWrapper : FrameworkElement {}
public class DataClass
{
public string Text { get; set; }
}
}
At least on my PC the lower text-block stays on the fallbackvalue
Update #2:
I tried the suggestion Matrin posted (deriving from DependencyObject, creating own DependencyProperty, etc) - it did not work either.
This time however the error-message is a different one:
"System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:(no path); DataItem=null; target element is 'TestWrapper' (HashCode=28415924); target property is 'TheData' (type 'Object')"
I also have some suggestions for workarounds though:
1.) - Use MultiBinding --> not compatible with Silverlight, not enough in some cases.
2.) - Use yet another wrapping object, set DataContext by hand in code-behind, like this --> fully compatible with Silverlight (apart from the fact, that you can't use a Framework-Element directly - you have to make an empty class deriving from it)
xaml:
<Window.Resources>
<FrameworkElement x:Key="DataContextWrapper" />
<local:TestWrapper x:Key="TestObj" DataContext="{Binding DataContext, Source={StaticResource DataContextWrapper}}" />
...
code behind:
//of course register this handler!
void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var dcw = this.Resources["DataContextWrapper"] as FrameworkElement;
dcw.DataContext = this.DataContext;
}
There may be a problem with your type being derived from FrameworkElement:
From the msdn page about suitable objects in ResourceDictionaries
[...] Being shareable is required [...]
Any object that is derived from the UIElement type is inherently not
shareable [...]
Derive from DependencyObject instead:
public class TestWrapper : DependencyObject {}

Custom resource dictionary inside ControlTemplate or DataTemplate

EDIT: This problem occurs when using the standard .NET ResourceDictionary as well and appears to be an issue with using resource dictionaries inside control or data templates.
I have a custom resource dictionary that follows a common approach to sharing resource instances.
http://softnotes.wordpress.com/2011/04/05/shared-resourcedictionary-for-silverlight/
http://www.wpftutorial.net/MergedDictionaryPerformance.html
public class SharedResourceDictionary : ResourceDictionary
{
static readonly Dictionary<Uri, WeakReference<ResourceDictionary>> SharedDictionaries = new Dictionary<Uri, WeakReference<ResourceDictionary>>();
Uri _sourceUri;
public new Uri Source
{
get
{
// Behave like standard resource dictionary for IDE...
if (VisualStudio.IsInDesignMode)
return base.Source;
return this._sourceUri;
}
set
{
// Behave like standard resource dictionary for IDE...
if (VisualStudio.IsInDesignMode)
{
base.Source = value;
return;
}
this._sourceUri = value;
WeakReference<ResourceDictionary> cached;
if (SharedDictionaries.TryGetValue(value, out cached))
{
ResourceDictionary rd;
if (cached.TryGetTarget(out rd))
{
this.MergedDictionaries.Add(rd);
return;
}
}
base.Source = value;
SharedDictionaries[value] = new WeakReference<ResourceDictionary>(this);
}
}
}
It works fine, but whenever it is referenced inside a Resources element within a ControlTemplate or DataTemplate, there are spurious errors shown (these do not affect the build, which still succeeds).
This one gets shown for the standard ResourceDictionary which contains SharedResourceDictionary in its merged dictionaries:
Unable to cast object of type 'Microsoft.Expression.Markup.DocumentModel.DocumentCompositeNode' to type 'System.Windows.ResourceDictionary'
Sample XAML:
<DataTemplate DataType="{x:Type vm:MyViewModel}">
<DockPanel Style="{DynamicResource MainDockPanel}">
<DockPanel.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<p:SharedResourceDictionary Source="/MyAssembly;component/MyResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</DockPanel.Resources>
</DockPanel>
</DataTemplate>
Does anyone have any ideas how I can eliminate this nuisance error?
Thanks
This issue has been reported to Microsoft. You can vote on it, so maybe it will get fixed in some future release.
https://connect.microsoft.com/VisualStudio/feedback/details/772730/xaml-designer-broken-when-adding-resource-dictionaries-to-data-or-control-templates

How to get a “null” from ObjectDataProvider in design mode?

I put the instance of one of the VMs in a resource dictionary like:
<ObjectDataProvider ObjectType="{x:Type WpfApplication1:MyViewModel}" x:Key="TheViewModel"/>
I bind the DataContext of some user controls to this:
<WpfApplication1:UserControl1 x:Name="UsrCtrl1" DataContext="{StaticResource TheViewModel}"/>
and it works fine at the runtime, because all connections and servers are available and a lot of logical objects are correctly initialized.
The problem is, in the design time I get a lot of exceptions (there are many such VMs), that make the work with very difficult.
Is it possible somehow to say in XAML if ComponentModel:DesignerProperties.IsInDesignMode (xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=PresentationFramework") is true then x:null, otherwise create my VM WpfApplication1:MyViewModel ???
I tried a lot, but was unable to get the right solution, but I cannot believe this is not possible. For any idea (maybe a tested example) thanks in advance.
The way I've dealt with this in the past involves providing an interface for your viewmodels and having the views ask for their viewmodel from a viewmodel locator class. For example, you'd have the following viewmodels:
public interface IMainViewModel
{
double Foo { get; }
double Bar { get; }
}
public class RealMainViewModel : IMainViewModel
{
// implementation of IMainViewModel, this one does your data access
// and is used at run time
}
public class FakeMainViewModel : IMainViewModel
{
// implementation of IMainViewModel, this one is fake
// and is used at design time
}
The viewmodel locator would look like the following:
public class ViewModelLocator
{
public static IMainViewModel MainViewModel
{
get
{
if (Designer.IsDesignMode)
{
return new FakeMainViewModel();
}
else
{
return new RealMainViewModel();
}
}
}
}
Finally, you'll include a reference to ViewModelLocator in App.xaml:
<Application.Resources>
<ResourceDictionary>
<yourNamespace:ViewModelLocator x:Key="ViewModelLocator" />
</ResourceDictionary>
</Application.Resources>
This way, you can bind to the viewmodel property in ViewModelLocator and have your code do the work for injecting the real and fake viewmodel when appropriate:
<WpfApplication1:UserControl1 x:Name="UsrCtrl1" DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}"/>
I also found an article that provides another example. Note that I wrote this code on-the-fly in Notepad so I apologize if there are any typos.
I believe you can use the following in your UserControl1 tag to define a Design-Time DataContext
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{x:Null}"
I haven't actually tested it since I usually don't use the designer window, but in theory it should work :)

WPF Standard Commands - Where's Exit?

I'm creating a standard menu in my WPF application.
I know that I can create custom commands, but I know there are also a bunch of standard commands to bind to.
For example, to open a file I should bind to ApplicationCommands.Open, to close a file I should bind to ApplicationCommands.Close. There's also a large number of EditCommands, ComponentCommands or NavigationCommands.
There doesn't seem to be an "Exit" command. I would have expected there to be ApplicationCommands.Exit.
What should I bind to the "Exit" menu item? To create a custom command for something this generic just seems wrong.
Unfortunately, there is no predefined ApplicationCommands.Exit. Adding one to WPF was suggested on Microsoft Connect in 2008: http://connect.microsoft.com/VisualStudio/feedback/details/354300/add-predefined-wpf-command-applicationcommands-exit. The item has been marked closed/postponed, however.
Mike Taulty discussed how to create your own Exit command in an article on his blog.
AFAIK there's no ApplicationCommands.Quit or ApplicationCommands.Exit, so I guess you're gonna have to create it yourself...
Anyway, if you're using the MVVM pattern, RoutedCommands are not exactly handy, so it's better to use a lightweight alternative like RelayCommand or DelegateCommand.
Not that complex actually (but still, M$ sucks for not providing it). Here you go:
public static class MyCommands
{
private static readonly ICommand appCloseCmd = new ApplicationCloseCommand();
public static ICommand ApplicationCloseCommand
{
get { return appCloseCmd; }
}
}
//===================================================================================================
public class ApplicationCloseCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
// You may not need a body here at all...
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return Application.Current != null && Application.Current.MainWindow != null;
}
public void Execute(object parameter)
{
Application.Current.MainWindow.Close();
}
}
And the body of the AplicationCloseCommand.CanExecuteChanged event handler may not be even needed.
You use it like so:
<MenuItem Header="{DynamicResource MenuFileExit}" Command="MyNamespace:MyCommands.ApplicationCloseCommand"/>
Cheers!
(You cannot imagine how long it took me to discover this Command stuff myself...)
Yes, it would've made a lot of sense for Microsoft to include an ApplicationCommands.Exit command in their collection of pre-defined commands. It disappoints me that they didn't. But as the answers to this question demonstrate, not all is lost.
There are lots of workarounds possible for the lack of a proper ApplicationCommands.Exit object. However, I feel most miss the point. Either they implement something in the view model, for something that really is strictly a view behavior (in some cases reaching into the view object graph with e.g. Application.Current.MainWindow!), or they write a bunch of code-behind to do what XAML does very well and much more conveniently.
IMHO, the simplest approach is just to declare a RoutedUICommand resource for the window, attach that to a command binding, and to a menu item, to hook all the parts up. For example:
<Window x:Class="ConwaysGameOfLife.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<RoutedUICommand x:Key="fileExitCommand" Text="File E_xit">
<RoutedUICommand.InputGestures>
<KeyGesture >Alt+F4</KeyGesture>
</RoutedUICommand.InputGestures>
</RoutedUICommand>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource fileExitCommand}" Executed="fileExitCommand_Executed"/>
</Window.CommandBindings>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<!-- other menu items go here -->
<Separator/>
<MenuItem Command="{StaticResource fileExitCommand}"/>
</MenuItem>
</Menu>
<!-- the main client area UI goes here -->
</DockPanel>
</Window>
The Executed event handler for the command binding is trivial:
private void fileExitCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
Close();
}
Assuming, of course, your program implementation follows the usual semantics of closing the main window to exit the program.
In this way, all of the UI-specific elements go right into the RoutedUICommand object, which can be configured correctly and conveniently right in the XAML rather than having to declare a new C# class to implement the command and/or mess around with the input bindings from code-behind. The MenuItem object already knows what to do with a RoutedUICommand in terms of display, so going this route provides a nice decoupling of the command's properties from the rest of the UI. It also provides a convenient way to provide a secondary key gesture, in case you'd prefer something other than the default Alt+F4 (e.g. Ctrl+W).
You can even put the RoutedUICommand declaration in the App.xaml file, for reuse among multiple windows in your program, should they exist. Again, decoupling the UI-specific aspects, which are declared in the resource, from the consumers that are found throughout the program.
I find this approach much more generalizable and easily implemented than the other options which I've seen presented (here and elsewhere).
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
Pretty simple to do:
using System.Windows.Input;
namespace YourApp
{
static class ApplicationCommands
{
public static RoutedCommand Quit { get; }
static ApplicationCommands()
{
var inputGestures = new InputGestureCollection();
inputGestures.Add(new KeyGesture(Key.Q, ModifierKeys.Control));
Quit = new RoutedUICommand("Quit", "Quit", typeof(ApplicationCommands),
inputGestures);
}
}
}
Use it in your XAML like this:
<Window.CommandBindings>
<CommandBinding Command="local:ApplicationCommands.Quit"
Executed="QuitCommandOnExecuted"/>
</Window.CommandBindings>
[..]
<MenuItem Command="local:ApplicationCommands.Quit" />
Code-behind:
void QuitCommandOnExecuted(object sender, ExecutedRoutedEventArgs e)
{
Application.Current.Shutdown();
}
You can modify the Text property of a command before you bind it in a window. This will change the text of the command everywhere it appears in your ui.
EDIT: Do this to Close in the Window or App ctor

Resources