WPF Always On Top - c#

Is it possible to make a window stay always on top even when other application is running on Fullscreen? I'm using right now TopMost = true but when other application is running on fullscreen mine becomes invisible. It's WindowStyle = None window by the way.
Edit: And do not let other window minimalize ofcourse

This won't work 100% of the time, but it will improve the situation somewhat. You can set Topmost = true in the handler for the Window.Deactivated event:
private void Window_Deactivated(object sender, EventArgs e)
{
Window window = (Window)sender;
window.Topmost = true;
}
The Deactivated event will be called whenever your application loses focus (often when another application requests to be Topmost) and so this will reset your application on top after this.

Try this solution from MSDN, it should work for you.
In the Window Activated Event add the following code:
this.Width = System.Windows.SystemParameters.PrimaryScreenWidth;
this.Height = System.Windows.SystemParameters.PrimaryScreenHeight;
this.Topmost = true;
this.Top = 0;
this.Left = 0;
in DeActivated Event add the following code
this.Topmost = true;
this.Activate();
Original post from MSDN

None of the solutions above for me worked, so here is what I ended up doing. It worked perfectly for me.
Basically, to keep it on top you just set the lose focus event to make it go back to top.
XAML:
PreviewLostKeyboardFocus="Window_PreviewLostKeyboardFocus"
Code Behind:
private void Window_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
var window = (Window)sender;
window.Topmost = true;
}

If you want your application to stay on top of EVERYTHING (including the start interface in Windows 8, previously known as "Metro"), then you can specify UiAccess="True" in your manifest file. This is typically used by accessibility applications such as onscreen keyboards.
From memory you need to do 3 things;
Request UiAccess="True"
Sign your application's exe file with a recognised certificate. I obtained a free code signing certificate from Certum as my project is Open Source.
Install your application to a "Trusted Location", which in my case was the program files directory. There is no official definition of "Trusted Location" that I could find.

So I ran into the same requirement recently. It seems the top rated answer as well as the second didn't properly work for me. I've found a solution that seems to work flawlessly and somewhat adheres to best practice using MVVM.
Using the below forces the window to the top and never lapses on change like the other solutions.
Step 1: I created a simple state manager class for my main client window. I used INotifyPropertyChanged to keep property in sync when using a direct binding to my window. (very important)
public class ClientStateManager : INotifyPropertyChanged
{
#region Private Variables
private bool isForceToTop;
private bool isClientEnabled;
#endregion
#region Public Properties
public bool IsForceToTop
{
get { return isForceToTop; }
set
{
isForceToTop = value;
NotifyPropertyChanged();
}
}
public bool IsClientEnabled
{
get { return isClientEnabled; }
set
{
isClientEnabled = value;
NotifyPropertyChanged();
}
}
#endregion
#region Private Methods
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Public Methods
public void Lock() => this.IsClientEnabled = false;
public void UnLock() => this.IsClientEnabled = true;
public void SetTop() => this.IsForceToTop = true;
public void UnSetTop() => this.IsForceToTop = false;
#endregion
#region Public Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Step 2.1: Added my state manager class to my ViewModel. (MVVM)
internal class MainWindowViewModel : INotifyPropertyChanged
{
#region Constructor
public MainWindowViewModel()
{
ClientStateManager = new ClientStateManager();
}
#endregion
#region Public Properties
public ClientStateManager ClientStateManager { get; private set; }
#endregion
}
Step 2.2: Then set your window data context to your view model.
private MainWindowViewModel model;
private MainWindow()
{
InitializeComponent();
this.model = new MainWindowViewModel();
this.DataContext = model;
}
Step 3: Add your data binding to your window.
<Window x:Class="Intouch_Work.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:ojects="clr-namespace:Framework.Object;assembly=Framework"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
Title="Intouch" Height="800" Width="1100"
x:Name="mainWindow"
Topmost="{Binding Path=ClientStateManager.IsForceToTop, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
So now you can manage your window state using the state manager object initialized within the View Model. You can call SetTop() from you state manager to push it forward, or UnSetTop() to stop it.
Hope this helps anyone looking to do the same.

I needed something similar for an industrial quality control application, where non-admin operators should not be able to minimize or use anything else on the dedicated computer while the application is running, not even Windows+D to bring up the desktop. And I found out that the cleanest and simplest way to achieve that is through:
Settings the correct Width, Height, WindowStyle, WindowState and Topmost properties.
Handling some related events: StateChanged, Deactivated, LostFocuse, LostMouseCapture, LostKeyboardFocus and PreviewLostKeyboardFocus.
Handling closing events with ALT+F4 or a custom button).
No need for P/Invoke, Here is the full code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += OnLoaded;
Closing += OnClosing;
StateChanged += OnStateChanged;
Deactivated += (sender, args) => Activate();
LostFocus += (sender, args) => Focus();
LostMouseCapture += (sender, args) => Mouse.Capture(this);
LostKeyboardFocus += (sender, args) => Keyboard.Focus(this);
PreviewLostKeyboardFocus += (sender, args) => Keyboard.Focus(this);
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Width = SystemParameters.PrimaryScreenWidth;
Height = SystemParameters.PrimaryScreenHeight;
WindowStyle = WindowStyle.None;
WindowState = WindowState.Maximized;
Topmost = true;
// Other stuff here
}
private void OnClosing(object sender, CancelEventArgs e)
{
// You might want to allow only some users to close the app
if (MessageBox.Show("Are you an admin?", "Admin Check", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.No)
e.Cancel = true;
}
private void OnStateChanged(object sender, EventArgs e)
{
if (WindowState == WindowState.Minimized)
WindowState = WindowState.Maximized;
}
}
You can also put some of this in XAML:
<Window x:Class="FullScreen.MainWindow"
...
Title="MainWindow"
WindowState="Maximized"
WindowStyle="None"
Topmost="True">
<Grid>
</Grid>
</Window>

I had a main window that I wanted to keep on top of everything (if the user checked "always on top".
This worked for me. Hope this helps someone.
// If we want main to stay on top, we set the rest of the menus to Not be top
if (mnuViewMainWindowAlwaysOnTopo.IsChecked)
{
this.Topmost = true;
foreach (var window in Application.Current.Windows)
// Don't change for main window
if (window.GetType().Name != this.GetType().Name)
window.Topmost = false;
}
else this.Topmost = false;

Related

detecting air-tap event in an 2D UWP application for Hololens 1

I am trying to detect an air-tap event in my 2D UWP application for Hololens 1. I am using VS 2019 for my development. I have followed some sample code from BasicHologram and Hands and motion controllers in DirectX. Here is my sample code I wrote:
SpatialInputHandler.cs
public class SpatialInputHandler
{
private SpatialInteractionManager interactionManager;
private SpatialInteractionSourceState sourceState;
public SpatialInputHandler()
{
interactionManager = SpatialInteractionManager.GetForCurrentView();
interactionManager.SourcePressed += this.OnSourcePressed;
}
public SpatialInteractionSourceState CheckForInput()
{
SpatialInteractionSourceState sourceState = this.sourceState;
this.sourceState = null;
return sourceState;
}
public void OnSourcePressed(SpatialInteractionManager sender, SpatialInteractionSourceEventArgs args)
{
sourceState = args.State;
}
}
MainPage.cs
public sealed partial class MainPage : Page
{
private SpatialInputHandler spatialInputHandler;
DispatcherTimer dispatcherTimer;
public MainPage()
{
this.InitializeComponent();
spatialInputHandler = new SpatialInputHandler();
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Interval = TimeSpan.FromMilliseconds(60);
dispatcherTimer.Tick += DispatcherTimer_Tick;
dispatcherTimer.Start();
}
private void DispatcherTimer_Tick(object sender, object e)
{
SpatialInteractionSourceState pointerState = spatialInputHandler.CheckForInput();
if (pointerState != null && pointerState.IsPressed)
{
textBlock2.Text = "airtap detected";
}
if(pointerState == null)
{
textBlock2.Text = "no airtap detected";
}
}
}
In my code I am always getting pointerState value as null. Could someone please help me how to figure this out. Thanks!
Because you are develeping a 2d flat app on HoloLens, the SpatialInteractionManager class is not designed for such scenario, it's for holographic apps.
For a flat app, it will run in the shell and as such we would need to handle routed events which are for input and user interaction scenarios(e.g. touch, mouse, etc…). For example, your UI might have a button(Button inherited from the UIElement base class) and handle the Click or Tap event:
XAML:
<Grid>
<Button Name="btnClick" Content="Click" Width="100" Height="50" Click="BtnClick_Click" Tapped="BtnClick_Tapped" />
</Grid>
Backend:
private void BtnClick_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Clicked");
}
private void BtnClick_Tapped(object sender, TappedRoutedEventArgs e)
{
Debug.WriteLine("Tapped");
}
These events can respond correctly with your air-tap interaction on HoloLens.
For more details, check our this documentation: Events and routed events overview
If you can share more details about how you want the air tap to interact with your app, we're happy to help you to move forward.

Set Window Top+Left in reference to Button position in WPF

Preamble
There are dozens of simular Questions to this topic on StackOverflow already and I browsed a lot without finding a suitable answer, that would apply to my problem.
Task
I have a WPF Window in a MVVM pattern, which has quite a lot of buttons that open other windows. I'd like to have most of my windows appear in relation to my buttons (I have a toolbar in the top right corner of my MainWindow and want most of the smaller windows to appear right below my buttons), or at least on the same screen as my MainWindow.
Problem
At first, I thought this wasn't such a big deal and there were plenty of blogs and questions on google to this topic, yet all of them won't work on my project.
I am using the MVVM pattern, which means:
I can't use Mouse.GetPosition(ButtonName) on my Buttons, as the ViewModel doesn't know their names
I can't use Mouse.GetPosition(sender) in a Click-Event, as most Buttons use commands.
I also apparently can't use PointToScreen in my view's code behind, as it will cause an exception (this visual object is not connected to a \"PresentationSource\")
I could use Mouse.GetPosition(this) on a MouseMove-Event in my view's code behind and hand it down to my ViewModel, which will update a Property, that I can use in my Commands when creating the window, but I don't like the idea of having to update a property permanently. Also without PointToScreen I can't set the point in relation to my screen.
I can't use any WinForms references, as this would cause conflicts in my current project
Additional to Buttons, I also host a UserControl with Hyperlinks in my MainWindow, which open additional windows, that should be in relation to the hyperlinks.
Research
there are quite a few different answers to a question here, but none of them did work for me.
As my ViewModel doesn't know the XAML elements I can't simply access by point notation as suggested here
My ViewModel doesn't know a WorkingArea, so I couldn't even get my window to appear on the same screen as my MainWindow as demonstrated here
As most of the other answers, this one seems like it won't work in a ViewModel
Question
I've spent quite some time on a problem, that rather seemed trivial at first, already. Since most questions I've viewed so far seem to target windows without MVVM, what would be the proper approach in a ViewModel to set the location of a window to either my mouse coordinates or the coordinates of a clicked button?
edit:
MouseDownEvent as requested in Comments:
Xaml:
<Window x:Class="MySampleProject.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:MySampleProject"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
MouseDown="Window_MouseDown">
C#:
private void Window_MouseDown(object sender, MouseEventArgs e)
{
if (m_oDataContext != null)
{
m_oDataContext.MouseTest(Mouse.GetPosition(this));
}
}
oDataContext is my ViewModel. My MouseTest() is currently empty. I did set a breakpoint at the first bracket. The breakpoint is only reached when left-clicking within my window, not within one of its hosted controls.
Here comes an example of how you can pass a parameter to Command in your Vm:
Window Class:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
DataContext= new MyVm();
}
private void BtnWin1_OnClick(object sender, RoutedEventArgs e)
{
var dataContext = DataContext as MyVm;
var relativePoint = ((Button)sender).TransformToAncestor(this).Transform(new Point(0, 0));
relativePoint.X += this.Left;
relativePoint.Y += this.Top;
dataContext?.OpenWindow1Command.Execute(relativePoint);
}
private void BtnWin2_OnClick(object sender, RoutedEventArgs e)
{
var dataContext = DataContext as MyVm;
var relativePoint = ((Button)sender).TransformToAncestor(this).Transform(new Point(0, 0));
relativePoint.X += this.Left;
relativePoint.Y += this.Top;
dataContext?.OpenWindow2Command.Execute(relativePoint);
}
}
VM Class:
public class MyVm : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ICommand OpenWindow1Command { get; }
public ICommand OpenWindow2Command { get; }
public MyVm()
{
OpenWindow1Command = new RelayCommand(OpenWindow1Command_Execute);
OpenWindow2Command = new RelayCommand(OpenWindow2Command_Execute);
}
void OpenWindow1Command_Execute(object parameter)
{
var point = (Point)parameter;
var win1 = new Window1{WindowStartupLocation = WindowStartupLocation.Manual, Left = point.X, Top = point.Y};
win1.Show();
}
void OpenWindow2Command_Execute(object parameter)
{
var point = (Point)parameter;
var win2 = new Window2 { WindowStartupLocation = WindowStartupLocation.Manual, Left = point.X, Top = point.Y };
win2.Show();
}
}
And Relay class if you haven't implemented that:
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action<object> execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute.Invoke();
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
You will loose the CanExecute functionality of the Command with this approach, but will do the work.

How to bring window to front with wpf and using mvvm

I have a window that essentially runs a timer. When the timer hits 0 I want to bring the window to the front so that it is visible and not hidden behind some other application.
From what I can gather I would simply call window.activate() to accomplish this but with mvvm my view model doesn't have a reference to window.
A "purist" MVVM solution is to use a behavior. Below is a behavior for a Window with an Activated property. Setting the property to true will activate the window (and restore it if it is minimized):
public class ActivateBehavior : Behavior<Window> {
Boolean isActivated;
public static readonly DependencyProperty ActivatedProperty =
DependencyProperty.Register(
"Activated",
typeof(Boolean),
typeof(ActivateBehavior),
new PropertyMetadata(OnActivatedChanged)
);
public Boolean Activated {
get { return (Boolean) GetValue(ActivatedProperty); }
set { SetValue(ActivatedProperty, value); }
}
static void OnActivatedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) {
var behavior = (ActivateBehavior) dependencyObject;
if (!behavior.Activated || behavior.isActivated)
return;
// The Activated property is set to true but the Activated event (tracked by the
// isActivated field) hasn't been fired. Go ahead and activate the window.
if (behavior.AssociatedObject.WindowState == WindowState.Minimized)
behavior.AssociatedObject.WindowState = WindowState.Normal;
behavior.AssociatedObject.Activate();
}
protected override void OnAttached() {
AssociatedObject.Activated += OnActivated;
AssociatedObject.Deactivated += OnDeactivated;
}
protected override void OnDetaching() {
AssociatedObject.Activated -= OnActivated;
AssociatedObject.Deactivated -= OnDeactivated;
}
void OnActivated(Object sender, EventArgs eventArgs) {
this.isActivated = true;
Activated = true;
}
void OnDeactivated(Object sender, EventArgs eventArgs) {
this.isActivated = false;
Activated = false;
}
}
The behavior requires a reference to System.Windows.Interactivity.dll. Fortunately, this is now available on NuGet in the Blend.Interactivity.Wpf package.
The behavior is attached to a Window in XAML like this:
<Window ...>
<i:Interaction.Behaviors>
<Behaviors:ActivateBehavior Activated="{Binding Activated, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
The view-model should expose a boolean Activated property. Setting this property to true will activate the window (unless it is already activated). As an added bonus it will also restore a minimized window.
You could go about it in a couple of ways - adding a reference to the window could work since the viewmodel is not coupled with the view but related to it, but I don't really like that approach since it pretty much does couple your view to your viewmodel - which is not really the point of MVVM
A better approach may be to have your viewmodel raise an event or a command which the view can handle. This way the view gets to decide what UI action is associated with the command/event
e.g. simply
class SomeView
{
void HandleSomeCommandOrEvent()
{
this.Activate();
}
}
Of course how you wire this up is up to you but I'd probably try and get routed commands happening
Edit: You can't really 'bind' a simple event, since it's invoked from the viewmodel.
A simple event based example is just to add the event to the viewmodel and handle it directly ... e.g. imagine the following MainWindow with a ViewModel property
public partial class MainWindow : Window
{
MainWindowViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new MainWindowViewModel();
ViewModel.ShowMessage += ViewModel_ShowMessage;
this.DataContext = ViewModel;
}
void ViewModel_ShowMessage(object sender, ShowMessageEventArgs e)
{
MessageBox.Show(e.Message, "Some caption", MessageBoxButton.OK);
}
}
Then the ViewModel can just fire the event:
// The view model
public class MainWindowViewModel
{
// The button click command
public RelayCommand ButtonClickCommand { get; set; }
// The event to fire
public event EventHandler<ShowMessageEventArgs> ShowMessage;
public MainWindowViewModel()
{
ButtonClickCommand = new RelayCommand(ButtonClicked);
}
void ButtonClicked(object param)
{
// This button is wired up in the view as normal and fires the event
OnShowMessage("You clicked the button");
}
// Fire the event - it's up to the view to decide how to implement this event and show a message
void OnShowMessage(string message)
{
if (ShowMessage != null) ShowMessage(this, new ShowMessageEventArgs(message));
}
}
public class ShowMessageEventArgs : EventArgs
{
public string Message { get; private set; }
public ShowMessageEventArgs(string message)
{
Message = message;
}
}
The XAML would be:
<Button Command="{Binding ButtonClickCommand}">Click me!</Button>
So the button invokes the command, which in turn fires the event which the view (MainWindow) handles and shows a messagebox. This way the view/UI decides on the course of action based on the type of event raised. Of course it could be your timer which fired the event
You can always go down the more involved route such as some of the answers on this question...
How should the ViewModel close the form?
but to be honest, it depends if you really need it - a simple event works well - some people overcomplicate things for the sake of elegance, but at the detriment of simplicity and productivity!
I would go this way:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
// View
public partial class TestActivateWindow : Window
{
public TestActivateWindow() {
InitializeComponent();
Messenger.Default.Register<ActivateWindowMsg>(this, (msg) => Activate());
}
}
// View Model
public class MainViewModel: ViewModelBase
{
ICommand _activateChildWindowCommand;
public ICommand ActivateChildWindowCommand {
get {
return _activateChildWindowCommand?? (_activateChildWindowCommand = new RelayCommand(() => {
Messenger.Default.Send(new ActivateWindowMsg());
}));
}
}
}
public class ActivateWindowMsg
{
}

How to determine whether my application is active (has focus)

Is there a way to tell whether my application is active i.e. any of its windows has .IsActive=true?
I'm writing messenger app and want it to flash in taskbar when it is inactive and new message arrives.
Used P/Invoke and loop
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
private static bool IsActive(Window wnd)
{
// workaround for minimization bug
// Managed .IsActive may return wrong value
if (wnd == null) return false;
return GetForegroundWindow() == new WindowInteropHelper(wnd).Handle;
}
public static bool IsApplicationActive()
{
foreach (var wnd in Application.Current.Windows.OfType<Window>())
if (IsActive(wnd)) return true;
return false;
}
You can subscribe to Main Window's Activated event, and then do whatever you want. Can you give it a try?
try this, override OnActivated method in your MainForm and do whatever you want
protected override void OnActivated(EventArgs e)
{
// TODO : Implement your code here.
base.OnActivated(e);
}
hop this help
You have the Activated and Deactivated events of Application.
If you want to be able to Bind to IsActive you can add a Property in App.xaml.cs
<TextBlock Text="{Binding Path=IsActive,
Source={x:Static Application.Current}}"/>
of course you can also access this property in code like
App application = Application.Current as App;
bool isActive = application.IsActive;
App.xaml.cs
public partial class App : Application, INotifyPropertyChanged
{
private bool m_isActive;
public bool IsActive
{
get { return m_isActive; }
private set
{
m_isActive = value;
OnPropertyChanged("IsActive");
}
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Activated += (object sender, EventArgs ea) =>
{
IsActive = true;
};
Deactivated += (object sender, EventArgs ea) =>
{
IsActive = false;
};
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
One way (may be there would be more better ways) can be to find the Active window through Windows API then find the process name of active window.
if(yourProcessName == ActiveWindowProcessName)
{
//your window is in focus
}
Another way could be to keep a reference of all the windows and when you want to find out whether your app is active or not just iterate through all the windows and check IsActive value
Another way could be to use OwnedWindows property of MainWindow. Whenever you are creating a new window assign main window it's owner. Then you can iterate all the OwnedWindows of MainWindow and check whether any is active or not.(never tried this approach)

Good or bad practice for Dialogs in wpf with MVVM?

I lately had the problem of creating add and edit dialogs for my wpf app.
All I want to do in my code was something like this. (I mostly use viewmodel first approach with mvvm)
ViewModel which calls a dialog window:
var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM);
// Do anything with the dialog result
How does it work?
First, I created a dialog service:
public interface IUIWindowDialogService
{
bool? ShowDialog(string title, object datacontext);
}
public class WpfUIWindowDialogService : IUIWindowDialogService
{
public bool? ShowDialog(string title, object datacontext)
{
var win = new WindowDialog();
win.Title = title;
win.DataContext = datacontext;
return win.ShowDialog();
}
}
WindowDialog is a special but simple window. I need it to hold my content:
<Window x:Class="WindowDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="WindowDialog"
WindowStyle="SingleBorderWindow"
WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight">
<ContentPresenter x:Name="DialogPresenter" Content="{Binding .}">
</ContentPresenter>
</Window>
A problem with dialogs in wpf is the dialogresult = true can only be achieved in code. That's why I created an interface for my dialogviewmodel to implement it.
public class RequestCloseDialogEventArgs : EventArgs
{
public bool DialogResult { get; set; }
public RequestCloseDialogEventArgs(bool dialogresult)
{
this.DialogResult = dialogresult;
}
}
public interface IDialogResultVMHelper
{
event EventHandler<RequestCloseDialogEventArgs> RequestCloseDialog;
}
Whenever my ViewModel thinks it's time for dialogresult = true, then raise this event.
public partial class DialogWindow : Window
{
// Note: If the window is closed, it has no DialogResult
private bool _isClosed = false;
public DialogWindow()
{
InitializeComponent();
this.DialogPresenter.DataContextChanged += DialogPresenterDataContextChanged;
this.Closed += DialogWindowClosed;
}
void DialogWindowClosed(object sender, EventArgs e)
{
this._isClosed = true;
}
private void DialogPresenterDataContextChanged(object sender,
DependencyPropertyChangedEventArgs e)
{
var d = e.NewValue as IDialogResultVMHelper;
if (d == null)
return;
d.RequestCloseDialog += new EventHandler<RequestCloseDialogEventArgs>
(DialogResultTrueEvent).MakeWeak(
eh => d.RequestCloseDialog -= eh;);
}
private void DialogResultTrueEvent(object sender,
RequestCloseDialogEventArgs eventargs)
{
// Important: Do not set DialogResult for a closed window
// GC clears windows anyways and with MakeWeak it
// closes out with IDialogResultVMHelper
if(_isClosed) return;
this.DialogResult = eventargs.DialogResult;
}
}
Now at least I have to create a DataTemplate in my resource file(app.xaml or something):
<DataTemplate DataType="{x:Type DialogViewModel:EditOrNewAuswahlItemVM}" >
<DialogView:EditOrNewAuswahlItem/>
</DataTemplate>
Well thats all, I can now call dialogs from my viewmodels:
var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM);
Now my question, do you see any problems with this solution?
Edit: for completeness. The ViewModel should implement IDialogResultVMHelper and then it can raise it within a OkCommand or something like this:
public class MyViewmodel : IDialogResultVMHelper
{
private readonly Lazy<DelegateCommand> _okCommand;
public MyViewmodel()
{
this._okCommand = new Lazy<DelegateCommand>(() =>
new DelegateCommand(() =>
InvokeRequestCloseDialog(
new RequestCloseDialogEventArgs(true)), () =>
YourConditionsGoesHere = true));
}
public ICommand OkCommand
{
get { return this._okCommand.Value; }
}
public event EventHandler<RequestCloseDialogEventArgs> RequestCloseDialog;
private void InvokeRequestCloseDialog(RequestCloseDialogEventArgs e)
{
var handler = RequestCloseDialog;
if (handler != null)
handler(this, e);
}
}
EDIT 2: I used the code from here to make my EventHandler register weak:
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(Website no longer exists, WebArchive Mirror)
public delegate void UnregisterCallback<TE>(EventHandler<TE> eventHandler)
where TE : EventArgs;
public interface IWeakEventHandler<TE>
where TE : EventArgs
{
EventHandler<TE> Handler { get; }
}
public class WeakEventHandler<T, TE> : IWeakEventHandler<TE>
where T : class
where TE : EventArgs
{
private delegate void OpenEventHandler(T #this, object sender, TE e);
private readonly WeakReference mTargetRef;
private readonly OpenEventHandler mOpenHandler;
private readonly EventHandler<TE> mHandler;
private UnregisterCallback<TE> mUnregister;
public WeakEventHandler(EventHandler<TE> eventHandler,
UnregisterCallback<TE> unregister)
{
mTargetRef = new WeakReference(eventHandler.Target);
mOpenHandler = (OpenEventHandler)Delegate.CreateDelegate(
typeof(OpenEventHandler),null, eventHandler.Method);
mHandler = Invoke;
mUnregister = unregister;
}
public void Invoke(object sender, TE e)
{
T target = (T)mTargetRef.Target;
if (target != null)
mOpenHandler.Invoke(target, sender, e);
else if (mUnregister != null)
{
mUnregister(mHandler);
mUnregister = null;
}
}
public EventHandler<TE> Handler
{
get { return mHandler; }
}
public static implicit operator EventHandler<TE>(WeakEventHandler<T, TE> weh)
{
return weh.mHandler;
}
}
public static class EventHandlerUtils
{
public static EventHandler<TE> MakeWeak<TE>(this EventHandler<TE> eventHandler,
UnregisterCallback<TE> unregister)
where TE : EventArgs
{
if (eventHandler == null)
throw new ArgumentNullException("eventHandler");
if (eventHandler.Method.IsStatic || eventHandler.Target == null)
throw new ArgumentException("Only instance methods are supported.",
"eventHandler");
var wehType = typeof(WeakEventHandler<,>).MakeGenericType(
eventHandler.Method.DeclaringType, typeof(TE));
var wehConstructor = wehType.GetConstructor(new Type[]
{
typeof(EventHandler<TE>), typeof(UnregisterCallback<TE>)
});
IWeakEventHandler<TE> weh = (IWeakEventHandler<TE>)wehConstructor.Invoke(
new object[] { eventHandler, unregister });
return weh.Handler;
}
}
This is a good approach and I used similar ones in the past. Go for it!
One minor thing I'd definitely do is make the event receive a boolean for when you need to set "false" in the DialogResult.
event EventHandler<RequestCloseEventArgs> RequestCloseDialog;
and the EventArgs class:
public class RequestCloseEventArgs : EventArgs
{
public RequestCloseEventArgs(bool dialogResult)
{
this.DialogResult = dialogResult;
}
public bool DialogResult { get; private set; }
}
I've been using an almost identical approach for several months now, and I'm very happy with it (i.e. I haven't yet felt the urge to rewrite it completely...)
In my implementation, I use a IDialogViewModel that exposes things such as the title, the standad buttons to show (in order to have a consistent apparence across all dialogs), a RequestClose event, and a few other things to be able to control the window size and behavior
If you are talking about dialogue windows and not just about the pop-up message boxes, please consider my approach below. The key points are:
I pass a reference to Module Controller into the constructor of each ViewModel (you can use injection).
That Module Controller has public/internal methods for creating dialogue windows (just creating, without returning a result). Hence to open a dialogue window in ViewModel I write: controller.OpenDialogEntity(bla, bla...)
Each dialogue window notifies about its result (like OK, Save, Cancel, etc.) via Weak Events. If you use PRISM, then it's easier to publish notifications using this EventAggregator.
To handle dialogue results, I'm using subscription to notifications (again Weak Events and EventAggregator in case of PRISM). To reduce dependency on such notifications, use independent classes with standard notifications.
Pros:
Less code. I don't mind using interfaces, but I've seen too many projects where excessiveness of using interfaces and abstraction layers cause more trouble than help.
Open dialogue windows through Module Controller is a simple way to avoid strong references and still allows to use mock-ups for testing.
Notification through weak events reduce number of potential memory leaks.
Cons:
Not easy to distinguish required notification from others in the handler. Two solutions:
send a unique token on opening a dialogue window and check that token in the subscription
use generic notification classes <T> where T is enumeration of entities (or for simplicity it can be type of ViewModel).
For a project should be an agreement about using notification classes to prevent duplicating them.
For enormously large projects the Module Controller can be overwhelmed by methods for creating windows. In this case it's better to split it up in several modules.
P.S. I have been using this approach for quite a long time now and ready to defend its eligibility in comments and provide some examples if required.

Categories

Resources