I has a requirement that to track down all Window.Show or ShowDialog() in WPF.
The main purpose is I want to know when all Window in App open or close.
Something like, when closing WindowA or ChildWindowA, I want to write AuditLog for which view was opened/closed, I don't want to write code for each Window or ChildWindow and write it in App instance level to handle all open/close Window or ChildWindow in App.
You could create a base class deriving from Window that handles the logging.
public class AuditLoggableWindow : Window
{
public AuditLoggableWindow()
{
Closing += OnClosing;
ContentRendered += OnShown;
}
protected void OnClosing(object o, CancelEventArgs e)
{
// log that window is closing now
}
protected void OnShown(object o, EventArgs e)
{
// log that the window has been shown
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : AuditLoggableWindow
{
public MainWindow()
{
InitializeComponent();
}
}
And in your XAML markup you need to Replace the Window tag with namespace:AuditLoggableWindow. As the namespace of my project is wpfApplication1, the markup would be as follows:
<wpfApplication1:AuditLoggableWindow x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
</Grid>
</wpfApplication1:AuditLoggableWindow>
I would like to register an attached property:
public static class WindowLog
{
public static readonly DependencyProperty EnableLogProperty =
DependencyProperty.RegisterAttached(
"EnableLog",
typeof(bool),
typeof(WindowLog),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.None, OnEnableWindowLogChanged));
public static void SetEnableWindowLog(Window window, bool value)
{
window.SetValue(EnableLogProperty, value);
}
public static bool GetEnableWindowLog(Window element)
{
return (bool)element.GetValue(EnableLogProperty);
}
private static void OnEnableWindowLogChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
Window window = dependencyObject as Window;
if (window == null)
{
return;
}
if (GetEnableWindowLog(window))
{
Register(window);
}
else
{
Unregister(window);
}
}
private static void Unregister(Window window)
{
window.Closing -= Window_Closing;
window.Activated -= Window_Activated;
window.Closed -= Window_Closed;
}
private static void Register(Window window)
{
window.Closing += Window_Closing;
window.Activated += Window_Activated;
window.Closed += Window_Closed;
}
private static void Window_Closed(object sender, EventArgs e)
{
Window window = (Window)sender;
window.Closing -= Window_Closing;
window.Activated -= Window_Activated;
window.Closed -= Window_Closed;
}
private static void Window_Activated(object sender, EventArgs e)
{
// do something
}
private static void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
// do something
}
}
Using
<Window x:Class="Wpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:attachments="clr-namespace:Wpf.Attachments"
attachments:WindowLog.EnableWindowLog="true">
<StackPanel>
</StackPanel>
Related
I'm working on a WPF application (a Reversi game) for school. I'm very new to MVVM, so excuse my noobishness.
I have a StartScreen.xaml bound to StartViewModel which represents the screen in the beginning of the game, and a BoardScreen.xaml bound to BoardViewModel, which is the screen with the Reversi board.
I use a ContentControl in MainWindow.xaml to display the screens. The ViewModels implement an abstract Screen class and I use a navigator to switch between them.
In my BoardScreen.xaml.cs, I'm trying to bind to an event from the BoardViewModel. The event is called GameEnded and is invoked when the game ends. I would like to subscribe to the event from the code behind (BoardScreen.xaml.cs) in order to play a sound when the game is ended. Something like this:
GameEnded += BoardScreen_GameEnded
...
private void BoardScreen_GameEnded(object sender, EventArgs e)
{
PlaySound(WinSound);
}
The problem is that I really don't know how to get to that event. I can't seem to access it using the DataContext or Content properties.
Does anyone have any idea how to go about this? If I need to clarify further, please ask. Thanks! :)
MainWindow.xaml
<Window x:Class="View.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"
xmlns:local="clr-namespace:View"
xmlns:vm="clr-namespace:ViewModel;assembly=ViewModel"
mc:Ignorable="d"
Title="Reversi"
SizeToContent="WidthAndHeight">
<ContentControl Content="{Binding CurrentScreen}" Margin="30" />
</Window>
App.xaml
<Application x:Class="View.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
xmlns:view="clr-namespace:View"
xmlns:vm="clr-namespace:ViewModel;assembly=ViewModel">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins\blue.xaml" />
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type vm:StartViewModel}">
<view:StartScreen />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:BoardViewModel}">
<view:BoardScreen />
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
Navigation.cs
public abstract class Screen
{
protected readonly Navigator navigator;
protected Screen(Navigator navigator)
{
this.navigator = navigator;
}
protected void SwitchTo(Screen screen)
{
this.navigator.CurrentScreen = screen;
}
}
public class Navigator : INotifyPropertyChanged
{
private Screen currentScreen;
public Navigator()
{
this.currentScreen = new StartViewModel(this);
}
public Screen CurrentScreen
{
get
{
return currentScreen;
}
set
{
this.currentScreen = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentScreen)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class SimpleCommand : ICommand
{
private readonly Action action;
public SimpleCommand(Action action)
{
this.action = action;
}
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
action();
}
}
BoardScreen.xaml
public partial class BoardScreen : UserControl
{
public System.Windows.Media.MediaPlayer ClickSound { get; private set; }
public System.Windows.Media.MediaPlayer WinSound { get; private set; }
public BoardScreen()
{
// Initialize sounds
ClickSound = new MediaPlayer();
WinSound = new MediaPlayer();
ClickSound.Open(new Uri(#"..\..\Sounds\click.mp3", UriKind.Relative));
WinSound.Open(new Uri(#"..\..\Sounds\win.mp3", UriKind.Relative));
}
private void BoardScreen_GameEnded(object sender, EventArgs e)
{
PlaySound(WinSound);
}
private void PlaySound(MediaPlayer sound)
{
sound.Stop();
sound.Position = TimeSpan.Zero;
sound.Play();
}
internal void PlayClickSound(object sender, RoutedEventArgs e)
{
PlaySound(ClickSound);
}
internal void PlayWinSound(object sender, RoutedEventArgs e)
{
PlaySound(WinSound);
}
}
UPDATE
As #Spongebrot mentioned in the comments, I was able to subscribe to the event by doing it inside the DataContextChanged event. Type checking is necessary, as I would get a NullPointerException otherwise.
DataContextChanged += (o, e) => {
if(DataContext is BoardViewModel)
{
var viewModel = DataContext;
(DataContext as BoardViewModel).GameEnded += BoardScreen_GameEnded;
}
};
I'm sure I'm overlooking something simple, but WPF is not something I generally work in, so I'm scratching my head a bit on this.
I am trying to show a "splash screen" for an application that is meant to run in the background. It's basically a process wrapper for Windows operating systems.
I have a WPF Window defined as such:
public partial class MainWindow : Window
{
private Timer _timer;
public MainWindow()
{
InitializeComponent();
ContentRendered += MainWindow_ContentRendered;
}
private void MainWindow_ContentRendered(object sender, EventArgs e)
{
_timer = new Timer(5000);
_timer.Elapsed += TimerOnElapsed;
_timer.Enabled = true;
_timer.Start();
}
private void TimerOnElapsed(object sender, ElapsedEventArgs e)
{
Dispatcher.Invoke(() =>
{
Hide();
Close();
});
}
~MainWindow()
{
_timer.Dispose();
}
}
public partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector {
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/MySolution.WindowsWrapper;component/mainwindow.xaml", System.UriKind.Relative);
#line 1 "..\..\MainWindow.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
this._contentLoaded = true;
}
}
XAML looks like:
<Window x:Class="MySolution.WindowsWrapper.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"
xmlns:local="clr-namespace:MySolution.WindowsWrapper"
mc:Ignorable="d"
Title="Splash" Height="100" Width="327" WindowStyle="None"
HorizontalAlignment="Center" VerticalAlignment="Center"
AllowsTransparency="True" WindowStartupLocation="CenterScreen" ShowInTaskbar="False" Topmost="True">
<Window.Background>
<ImageBrush ImageSource="my-logo-large.png"/>
</Window.Background>
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
</Grid>
</Window>
...in my Program.cs file, I have something like this:
[STAThread]
public static void Main(string[] args)
{
var splashScreen = new MainWindow();
splashScreen.Show();
SetupSystemTrayIcon();
var showConsole = args.Any(x => x.ToLowerInvariant().Contains("showconsole"));
StartService(showConsole);
WaitToDie();
}
The splash screen displays as expected... but the timer is never initialized, and never fires, so it never goes away.
From what I've read, the ContentRendered event is supposed to be fired when the window is shown... but that doesn't seem to happen. What am I doing incorrectly?
I suggest that you remove your custom Main method and move your code to the App.xaml.cs class's OnStartup method:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
SetupSystemTrayIcon();
var showConsole = args.Any(x => x.ToLowerInvariant().Contains("showconsole"));
StartService(showConsole);
}
}
Make sure that the StartupUri property is set to MainWindow.xaml in your App.xaml:
<Application x:Class="WpfApp2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp2"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
Then you should get a dispatcher and a window that gets activated.
I have a WPF application which shows a window as a DialogBox.
I want to close this DialogBox when I click anywhere on MainWindow in application.
Here I am adding one sample example to explain
MainWindow of application
<Window x:Class="WpfApplication1.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"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Open new Window" Click="Button_Click" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Window>
Code behind main Window
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Window1 temp_Window = new Window1();
temp_Window.ShowDialog();
}
}
}
Child window Which I try to close
<Window x:Class="WpfApplication1.Window1"
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"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="Window1" Height="300" Width="300" Deactivated="Window_Deactivated">
<Grid>
<TextBlock>New Window</TextBlock>
</Grid>
</Window>
Code behind This Child Window
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Window_Deactivated(object sender, EventArgs e)
{
Close();
}
}
}
Thanks
you can do this with Deactivated event of your DialogBox window
something like this
XAML
<Window x:Class="WpfApplication1.Window1"
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"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="Window1" Height="200" Width="200" WindowStartupLocation="CenterScreen"
Deactivated="Window_Deactivated">
<Grid>
</Grid>
</Window>
Code
private void Window_Deactivated(object sender, EventArgs e)
{
this.Close();
}
If you call the DialogBox with ShowDialog the background window (which is the MainWindow) will be disabled until you close the DialogBox and it won't get any click, so there is no way for you to achieve what you want unless you call the DialogBox with Show method which won't lock the background window, then you can close the DialogBox whenever MainWindow GotFocus fires.
first declare your temp_window as a filed not as a local variable:
private Window1 temp_Window;
When you open the new window don't open it as a dialog otherwise, the events on the mainWindow will not be handled use Show() instead of ShowDialog() :
private void Button_Click(object sender, RoutedEventArgs e)
{
temp_Window = new Window1();
temp_Window.Show();
}
On your main window handle the mouseDown event:
MouseDown="MainWindow_OnMouseDown"
and close the tempWindow in the handler
private void MainWindow_OnMouseDown(object sender, MouseButtonEventArgs e)
{
tempWindow .Close();
}
You have to declare your second window as a global variable and call the .Hide() and .Show() commands like that:
MainWindow:
public partial class MainWindow : Form
{
private Dialog m_Dialog;
public MainWindow()
{
InitializeComponent();
m_Dialog = new Dialog();
this.Click += closeDialog;
}
private void closeDialog(object sender, EventArgs e)
{
m_Dialog.Hide();
}
private void button1_Click(object sender, EventArgs e)
{
m_Dialog.Show();
}
}
It works just fine.
UPDATED:
Here is the same code for WPF. I did it in WinForms previously:
public partial class MainWindow : Window
{
private Dialog m_Dialog;
public MainWindow()
{
InitializeComponent();
m_Dialog = new Dialog();
this.MouseDoubleClick += onCloseDialog;
}
private void onCloseDialog(object sender, MouseButtonEventArgs e)
{
m_Dialog.Hide();
}
private void onButton(object sender, RoutedEventArgs e)
{
m_Dialog.Show();
}
}
You can use another event instead of MouseDoubleClick of course.
If you really want to close the window you would have to use the .Close() command and call the constructor of you window everytime.
I have a WPF window that focuses a textbox when the window starts up via the FocusManager, which works. e.g.
<Window
...
FocusManager.FocusedElement="{Binding ElementName=nameTextBox}">
Upon focus, I'd like to also highlight the text in the textbox. I'd like to do this all in the XAML if possible, as I have no code in code behind and would like to keep it that way.
I know you want to keep .cs out of your project but it'll be quite hard to achieve, you can create a one off class with an attached property inside that can be used over and over in your xaml files.
public class AutoFocusBehavior : DependencyObject
{
public static bool IsAutoFocus(DependencyObject obj)
{
return (bool)obj.GetValue(IsAutoFocusProperty);
}
public static void SetIsAutoFocus(DependencyObject obj, bool value)
{
obj.SetValue(IsAutoFocusProperty, value);
}
public static readonly DependencyProperty IsAutoFocusProperty =
DependencyProperty.RegisterAttached("IsAutoFocus", typeof(bool), typeof(AutoFocusBehavior),
new PropertyMetadata(false, new PropertyChangedCallback((d, de) =>
{
if ((bool)de.NewValue)
{
FrameworkElement frameworkElement = (FrameworkElement)d;
frameworkElement.Unloaded += frameworkElement_Unloaded;
frameworkElement.IsVisibleChanged += new DependencyPropertyChangedEventHandler(frameworkElement_IsVisibleChanged);
frameworkElement.IsEnabledChanged += frameworkElement_IsEnabledChanged;
}
else
{
FrameworkElement frameworkElement = (FrameworkElement)d;
frameworkElement.Unloaded -= frameworkElement_Unloaded;
frameworkElement.IsVisibleChanged -= new DependencyPropertyChangedEventHandler(frameworkElement_IsVisibleChanged);
frameworkElement.IsEnabledChanged -= frameworkElement_IsEnabledChanged;
}
})));
static void frameworkElement_Unloaded(object sender, RoutedEventArgs e)
{
FrameworkElement frameworkElement = (FrameworkElement)sender;
frameworkElement.IsVisibleChanged -= new DependencyPropertyChangedEventHandler(frameworkElement_IsVisibleChanged);
frameworkElement.IsEnabledChanged -= frameworkElement_IsEnabledChanged;
}
static void frameworkElement_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (((bool)e.NewValue))
{
if (sender is System.Windows.Controls.TextBox)
{
System.Windows.Controls.TextBox textBox = sender as System.Windows.Controls.TextBox;
if (textBox != null)
{
textBox.SelectAll();
}
}
((FrameworkElement)sender).Focus();
}
}
static void frameworkElement_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (((bool)e.NewValue))
{
if (sender is System.Windows.Controls.TextBox)
{
System.Windows.Controls.TextBox textBox = sender as System.Windows.Controls.TextBox;
if (textBox != null)
{
textBox.SelectAll();
textBox.Focus();
}
}
((FrameworkElement)sender).Focus();
}
}
}
Then used in any xaml like this.
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication3"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Text="testing" local:AutoFocusBehavior.IsAutoFocus="True" Margin="133,87,292,198"/>
</Grid>
</Window>
I've made a simple custom control with ClickEvent:
ImageButton.xaml:
<UserControl x:Name="ImgButton" x:Class="WpfApplication1.ImageButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid></Grid>
</UserControl>
ImageButton.xaml.cs:
public partial class ImageButton : UserControl
{
private bool mouse_down = false;
private bool mouse_in = false;
public event EventHandler Click;
public ImageButton()
{
this.MouseEnter += new MouseEventHandler(ImageButton_MouseEnter);
this.MouseLeave += new MouseEventHandler(ImageButton_MouseLeave);
this.MouseLeftButtonDown += new MouseButtonEventHandler(ImageButton_MouseLeftButtonDown);
this.MouseLeftButtonUp += new MouseButtonEventHandler(ImageButton_MouseLeftButtonUp);
InitializeComponent();
}
void ImageButton_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if ((mouse_down)&&(mouse_in))
{
Click(this, null);
}
mouse_down = false;
}
void ImageButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
mouse_down = true;
}
void ImageButton_MouseLeave(object sender, MouseEventArgs e)
{
mouse_in = false;
}
void ImageButton_MouseEnter(object sender, MouseEventArgs e)
{
mouse_in = true;
}
}
It works correct when I click on the control if I'm handling Click event, otherwise I get crash. So, what should I do?
You must check if handler was added to event beforre rising it:
void ImageButton_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if ((mouse_down) && (mouse_in) && Click != null)
{
Click(this, null);
}
mouse_down = false;
}