My goal is to output a list in a datagrid, but this doesn't work and the datagrid is empty.
I tried to display the list in an other way and it did (but I can't remember what it was) and it worked, except for it not being in a datagrid but just data. I have changed up some things, but back then it reached the end and got displayed.
ViewModel in Mainwindow:
public class ViewModel
{
public List<ssearch> Items { get; set; }
private static ViewModel _instance = new ViewModel();
public static ViewModel Instance { get { return _instance; } }
}
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
//For simplicity, let's say this window opens right away
var Mdata = new MDataWindow { DataContext = DataContext };
Mdata.Show();
}
Other Window for data display:
string searchParam = "status = 1";
public MDataWindow()
{
InitializeComponent();
}
private void AButton_Click(object sender, RoutedEventArgs e)
{
MainWindow.ViewModel.Instance.Items = Search(searchParam);
}
public List<ssearch> Search(string where)
{
{
//Lots of stuff going on here
}
return returnList;
}
And in WPF:
<Window x:Class="WPFClient.MDataWindow"
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:WPFClient"
mc:Ignorable="d"
Title="MDataWindow" Height="Auto" Width="Auto">
<StackPanel>
<Button x:Name="AButton" Click="AButton_Click" Content="Load" />
<DataGrid ItemsSource="{Binding Items}" />
</StackPanel>
</Window>
I have no clue where the error is and tried to strip the code down as much as possible without killing error sources. The Datagrid just stays empty when I press the "Load" button.
EDIT:
I tried to convert the list into an observableColletion before passing it to the ViewModel, but this didn't work. I am working with a library, which I am not sure how to use observableCollection with, so I converted it instead of using it right away:
VM:
public ObservableCollection<Product> Items { get; set; }
Data Window:
List<Product> pp = Search_Products(searchParam);
var oc = new ObservableCollection<Product>(pp);
MainWindow.ViewModel.Instance.Items = oc;
First, change your List<Product> to an ObservableCollection<Product> as this will help to display the Items of the list on Add/Remove immediately.
This is because ObservableCollection implements the INotifyCollectionChanged interface to notify your target(DataGrid) to which it is bound, to update its UI.
Second, your binding can never work as expected due to changed reference of your collection.
private void AButton_Click(object sender, RoutedEventArgs e)
{
// You are changing your Items' reference completely here, the XAML binding
// in your View is still bound to the old reference, that is why you're seeing nothing.
//MainWindow.ViewModel.Instance.Items = Search(searchParam);
var searchResults = Search(searchParam);
foreach(var searchResult in searchResults)
{
MainWindow.ViewModel.Instance.Items.Add(searchResult);
}
}
Make sure you have changed the List to ObservableCollection upon running the Add loop, else you will get an exception saying the item collection state is inconsistent.
The ViewModel class should implement the INotifyPropertyChanged interface and raise its PropertyChanged event whenever Items is set to a new collection:
public class ViewModel : INotifyPropertyChanged
{
private List<ssearch> _items;
public List<ssearch> Items
{
get { return _items; }
set { _items = value; OnPropertyChanged(); }
}
private static ViewModel _instance = new ViewModel();
public static ViewModel Instance { get { return _instance; } }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
This is required to notify the view regardless of the type of Items.
If you change the type of Items to ObservableCollection<T>, you should initialize the collection in the view model once:
public class ViewModel
{
public ObservableCollection<ssearch> Items { get; } = new ObservableCollection<ssearch>();
private static ViewModel _instance = new ViewModel();
public static ViewModel Instance { get { return _instance; } }
}
...and then add items to this collection instead of setting the property to a new one:
private void AButton_Click(object sender, RoutedEventArgs e)
{
MainWindow.ViewModel.Instance.Items.Clear();
var search = Search(searchParam);
if (search != null)
foreach (var x in search)
MainWindow.ViewModel.Instance.Items.Add(x);
}
I have created a WPF App using MVVM and I'm having difficulty with closing/opening windows. On my Login Window, I use the following method to close the Login Window and Open the WindowOPHome Window with a button click:
WindowOPHome dashboard = new WindowOPHome();
dashboard.Show();
Application.Current.MainWindow.Close();
Everything works fine and the Login Window closes while the WindowOPHome Window opens. When I try to close the WindowOPHome Window and open the WindowMainAdmin Window with a button click similar to the Login Window/WindowOPHome action, the WindowMainAdmin Window opens for a split second then disappears while the WindowOPHome never leaves sight. Following is code for closing WindowOPHome and opening WindowMainAdmin:
WindowMainAdmin dashboard = new WindowMainAdmin();
dashboard.Show();
Application.Current.MainWindow.Close();
Any help would be greatly appreciated! Please let me know if there any other pieces of code you need. Thank you so much!
I'd suggest you explicitly close the window you want to close rather than assuming it's the current main window.
With MVVM there are lots of different ways to do it, you can use an attached behavior or pass the window to the view model via the command parameter like this:
in the button xaml for the view:
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
in the command execute method in the view model:
if (parameter is System.Windows.Window)
{
WindowMainAdmin dashboard = new WindowMainAdmin();
dashboard.Show();
(parameter as System.Windows.Window).Close();
}
.
Alternatively, you could iterate all windows until you find the one you want.
foreach( Window window in Application.Current.Windows ) {
if(window is WindowOPHome)
{
window.Close();
break;
}
}
You might want to check on some other property other than just closing the first one of that type if you need to have more than one instance of a window open.
You could even adapt that to be a static close method within each window class.
I think when you create the Admin Window, the program considers your Admin Window to be your Current Main Window and closes it. To avoid this, you can explicitly close the window you want. I suggest implementing a MainViewModel to manage all your windows. This example assumes you only want one window to be open.
In View (Any Window):
private void OnClose(object sender, RoutedEventArgs e)
{
//ICommand Implemnation that informs MainViewModel of UserInput
//In this case, the command ShowOPHome is an Enum
inputhandler.Execute(MyCommands.ShowOPHome);
}
In ViewModel:
BaseWindow dashboard;
....
public void ShowWindow(MyCommands Param)
{
//Verify Parameter
....
if(!(dashboard is null))
dashboard.Close();
switch(Param)
{
case MyCommands.ShowOPHome:
dashboard = new WindowOPHome();
break;
case MyCommands.ShowMainAdmin:
dashboard = new WindowMainAdmin();
break;
}
dashboard.Show();
}
InputHandler:
public class Inputhandler : ICommand
{
...
public class Execute(object Data)
{
...
mainViewModel.ShowWindow(MyCommands.ShowOPHome);
...
}
...
}
You can create a MVVM pure solution to this issue using a couple of helper classes
public static class ViewCloser
{
public static readonly DependencyProperty DialogResultProperty = DependencyProperty.RegisterAttached(
"DialogResult",
typeof(bool?),
typeof(ViewCloser),
new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
var view = target as Window;
if (view == null)
return;
if (view.IsModal())
view.DialogResult = args.NewValue as bool?;
else
view.Close();
}
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
public static class WindowExtender
{
public static bool IsModal(this Window window)
{
var fieldInfo = typeof(Window).GetField("_showingAsDialog", BindingFlags.Instance | BindingFlags.NonPublic);
return fieldInfo != null && (bool)fieldInfo.GetValue(window);
}
}
In the application, first create a property in the ViewModel
private bool? _viewClosed;
public bool? ViewClosed
{
get { return _viewClosed; }
set {
_viewClosed = value);
RaisePropertyChanged("ViewClosed");
}
}
then bind to it in the View, using our helper class.
<Window x:Class="
...
vhelpers:ViewCloser.DialogResult="{Binding ViewClosed}"
...
>
All of these are great solutions! I chose cjmurph's solution because it was very simple (for my feeble mind) and I can easily adapt it for future uses. See implemented code below. Thanks again, everyone!
XAML
Command="{Binding BtnMainAdminPageGO}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
WindowOPMainViewModel
private ICommand _btnMainAdminPage;
public ICommand BtnMainAdminPageGO
{
get
{
if (_btnMainAdminPage == null)
{
_btnMainAdminPage = new RelayCommand(param => this.BtnMainAdminPage(), null);
}
return _btnMainAdminPage;
}
}
private void BtnMainAdminPage()
{
WindowMainAdmin dashboard = new WindowMainAdmin();
dashboard.Show();
foreach(Window window in Application.Current.Windows)
{
if (window is WindowOPHome)
{
window.Close();
break;
}
}
}
I'm new in C# and created a simple app that works fine, but I wanna to learn C# using the pattern MVVM. So I'm trying to migrate my app to MVVM and I become confused
My Goal is simple:
1) When opened, the app scan a folder and index all the files in the format
"[number] [name]" - It's working fine!
2) I have a window with just one textBox. The user types a number and press ENTER. In this moment I have a CatalogViewModel that is a set o File and should select the file specified by the number in textBox and open it.
Problem 1: In MVVM I can't pass the data from my view Main to my ViewModel CatalogViewModel (I'm not sure if I made correct)
Problem 2: I can't handle the ENTER key and trigger a function inside CatalogViewModel
I'm a little confused with MVVM and can't go ahead. I know that it's simple. Please, how to resolve this 2 problem (in detail please, I'm a beginner in C# and in all its concept)
UPDATE 1:
tryed the janonimus' Solution to problem 1 But the databind is just ONE WAY.
The value from VM goes to the VIEW, but the changes on the VIEW don't to to VM.
I've implemented INotifyPropertyChanged this way
using Prism.Mvvm;
...
public class CatalogViewModel: BindableBase
{
private string selectedValue = "100";
public string SelectedValue
{
get { return selectedValue; }
set { SetProperty(ref selectedValue, value); }
}
But the Databind becomes just ON WAY
the XAML
<TextBox x:Name="tbSelectedValue" Text="{Binding SelectedValue, Mode=TwoWay}"
UPDATE 2
I found the solution for the problem 1. The code provided by janonimus works just in ONE WAY because the default behavior of TextBox.Text is to upadte when it lose the focus, but in my case it will never lose the focus see this post
The following code resolved the problem 1:
Text="{Binding Path=SelectedValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
The problem 2 Can be resolved with Pedro Silva's answer
if (e.Key == Key.Enter && tbSelectedValue.Text != String.Empty)
{
vm.OpenSelectedFile();
tbSelectedValue.Text = String.Empty;
}
But I wanna to implement it using a more sofisticated way, using ICommand.
Following the advise sent by janonimus, I created the BaseCommand Class Exactly like this but it throws a mismatch error when I call the function OpenSelectedFile
private BaseCommand<CatalogViewModel> _selectFileCommand;
public ICommand SelectFileCommand
{
get
{
if (_selectFileCommand == null)
{
_selectFileCommand = new BaseCommand<CatalogViewModel>(OpenSelectedFile, true);
}
return _selectFileCommand;
}
}
public void OpenSelectedFile()
{
try
{
OpenFileByNumber(Int32.Parse(SelectedValue));
}
catch (Exception e)
{
MessageBox.Show("Número inválido: \'" + SelectedValue + "\"",
"ERRO", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
That's my code for reference...
Main.xaml.cs
namespace SLMT.Views
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class Main : Window
{
public Main()
{
InitializeComponent();
DataContext = new CatalogViewModel();
chosenNumber.Focus();
}
// Permite inserir somente números
private void ChosenNumber_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
Regex regex = new Regex("[^ 0-9]+");
e.Handled = regex.IsMatch(e.Text);
}
private void ChosenNumber_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && chosenNumber.Text != String.Empty)
{
//catalog.OpenFileByNumber(ConvertToInt(numeroEscolhido.Text));
//catalog.OpenSelectedFile(); // Will become someting like this
chosenNumber.Text = String.Empty;
}
}
private int ConvertToInt(string value)
{
try
{
var str = value.Replace(" ", String.Empty);
return Int32.Parse(str);
}
catch (Exception exc)
{
MessageBox.Show("O número: \"" + chosenNumber.Text + "\" é inválido", "ERRO", MessageBoxButton.OK, MessageBoxImage.Error);
chosenNumber.Text = String.Empty;
return 0;
}
}
/// <summary>
/// Controll what wil lhappen if some KEYS are pressed on APP
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void GMain_KeyUp(object sender, KeyEventArgs e)
{
switch (e.Key){
case Key.Escape:
Environment.Exit(0);
break;
case Key.F1:
//wListFiles = new ListFiles(catalog);
//wListFiles.ShowDialog();
//numeroEscolhido.Text = wListFiles.SelectFile();
//numeroEscolhido.SelectAll();
break;
}
}
}
}
ps: The commented lines, I imported from the version without MVVM
Main.xaml
<Window x:Name="wMain" x:Class="SLMT.Views.Main"
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:SLMT.Views"
mc:Ignorable="d"
Title="Ministério Tons" Height="364" Width="700" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" WindowStyle="None">
<Grid x:Name="gMain" KeyUp="GMain_KeyUp">
<Image x:Name="imgBackground" HorizontalAlignment="Left" Height="364" VerticalAlignment="Top" Width="700" Source="/SLMT;component/Resources/img/background2.jpg" Opacity="100"/>
<TextBox x:Name="chosenNumber" HorizontalAlignment="Center" Height="34" Margin="500,294,56,36" TextWrapping="Wrap" VerticalAlignment="Center" Width="144" BorderBrush="{x:Null}" Background="{x:Null}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" UndoLimit="50" ForceCursor="True" PreviewTextInput="ChosenNumber_PreviewTextInput" KeyUp="ChosenNumber_KeyUp" BorderThickness="0" FontSize="20" Opacity="0.6" FontWeight="Bold"/>
</Grid>
</Window>
And the Relevant part of CatalogViewModel.cs
namespace SLMT.ViewModel
{
public class CatalogViewModel: ObservableCollection <File>
{
private int selectedNumber;
/// <summary>
/// Contain the selected number in the View
/// </summary>
public int SelectedNumber
{
get { return selectedNumber; }
set { selectedNumber = value; }
}
// REMOVED CODE TO SCAN AND INDEX THE FILES
public CatalogViewModel() : base()
{
ScanFiles();
ValidateAndAddFiles();
ShowAlerts();
}
public void OpenSelectedFile()
{
OpenFileByNumber(SelectedNumber);
}
/// <summary>
/// Get the file from catalog identified my the number
/// </summary>
/// <param name="number"></param>
/// <returns>File|null</returns>
private File GetFileByNumber(int number)
{
foreach (var file in this)
{
if (file.number == number){
return file;
}
}
return null;
}
private void OpenFileByNumber(int number)
{
var file = GetFileByNumber(number);
if (file == null)
{
MessageBox.Show("Nenhum arquivo encontrado com o número: \'" + number +"\"",
"ARQUIVO NÃO ENCONTRADO", MessageBoxButton.OK, MessageBoxImage.Warning);
} else
{
file.Open();
}
}
}
}
Try the following to have access to the view model that you created in your constructor.
CatalogViewModel vm = new CatalogViewModel();
public Main()
{
InitializeComponent();
DataContext = vm;
chosenNumber.Focus();
}
Then in your key handler, you can do this:
private void ChosenNumber_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && chosenNumber.Text != String.Empty)
{
//catalog.OpenFileByNumber(ConvertToInt(numeroEscolhido.Text));
//catalog.OpenSelectedFile(); // Will become someting like this
vm.OpenSelectedFile();
chosenNumber.Text = String.Empty;
}
}
Update: Using ICommand
Using the BaseCommand class you pointed to from here. I added the following code to the CatalogViewModel and got it working. You had the type in BaseCommand as the view model, but that's supposed to be the type of the command parameter (based on examples in that post).
private BaseCommand<object> _selectFileCommand;
public ICommand SelectFileCommand
{
get
{
if (_selectFileCommand == null)
{
_selectFileCommand = new BaseCommand<object>((commandParam) => OpenSelectedFile(commandParam),
(commandParam) => CanOpenSelectedFile(commandParam));
}
return _selectFileCommand;
}
}
public void OpenSelectedFile(object commandParam = null)
{
Debug.WriteLine("CatalogViewModel.OpenSelectedFile was called.");
Debug.WriteLine("SelectedValue = " + this.SelectedValue);
}
public bool CanOpenSelectedFile(object commandParam = null)
{
return true;
}
Once you get into the OpenSelectedFile method, you should be able to hook it up to the functionality that you want.
You need to do a few things here.
For problem 1, you must bind the View that will pass the data to ViewModel.
In this case, the TextBox.Text property must bind to CatalogViewModel.SelectedNumber property:
<TextBox x:Name="chosenNumber"
...
Text={Binding SelectedNumber} />
For MVVM's sake, the CatalogViewModel class must implement the INotifyPropertyChanged interface. And just create a property for the ObservableCollection that you have.
For problem 2, you're going to need KeyBinding and ICommand to make this work.
In the View, it should look like this:
<TextBox x:Name="chosenNumber"
...
Text={Binding SelectedNumber}>
<TextBox.InputBindings>
<KeyBinding Key="Enter"
Command="{Binding SelectFileCommand}" />
</TextBox.InputBindings>
</TextBox>
In the ViewModel, you will need an ICommand property:
public class CatalogViewModel: INotifyPropertyChanged
{
private BaseCommand _selectFileCommand;
public ICommand SelectFileCommand
{
get
{
if (_selectFileCommand == null)
{
_selectFileCommand = new BaseCommand(SelectFile, CanSelectFile);
}
return _selectFileCommand;
}
}
...
Where, SelectFile is a function that will do the action, CanSelectFile is function tells whether or not the command can execute, BaseCommand is an implementation of the ICommand interface.
You can refer to this question: WPF selectedItem on Menu or get commandparameter in viewmodel
UPDATE:
Use this BaseCommand implementation, and not the BaseCommand<T>:
class BaseCommand : ICommand
{
private readonly Action _executeMethod = null;
private readonly Func<bool> _canExecuteMethod = null;
public BaseCommand(Action executeMethod, Func<bool> canExecuteMethod)
{
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute()
{
if (_canExecuteMethod != null)
{
return _canExecuteMethod();
}
return true;
}
public void Execute()
{
if (_executeMethod != null)
{
_executeMethod();
}
}
}
Give this a shot and let us know what happens.
my solutions contains a library called Manager (with one class called ProductionManager) as well as a WPF project in it.
I would like one method of the ProductionManager class to access a UIcontrol of the WPF, how can I do it?
To be more specific my UI there is a button and a TextBox. Pressing the button, I call a method from my library from which I would like to update the TextBox in the UI Window.
I know I should use the Dispatcher to do so but I cannot figure out how to set it up correctly. Can anyone help please?
Here the MainWindow.xaml:
<Window x:Class="WPFMainPanel.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">
<Grid>
<Button Name="btnRun" Content="Run" HorizontalAlignment="Left" Margin="157,10,0,0" VerticalAlignment="Top" Width="75" Click="btnRun_Click"/>
<TextBox Name="tbox_Data" HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
</Grid>
</Window>
Here the MainWindow.xaml.cs:
namespace WPFMainPanel
{
public partial class MainWindow : Window
{
private ProductionManager myManager = ProductionManager.Instance;
private void btnRun_Click(object sender, RoutedEventArgs e)
{
myManager.DoSomethingElse();
}
}
}
and here my ProductionManager class in my Manager Library
namespace Manager
{
public class ProductionManager
{
private static ProductionManager instance;
private ProductionManager() { }
public static ProductionManager Instance
{
get
{
if (instance == null)
{
instance = new ProductionManager();
}
return instance;
}
}
public void DoSomethingElse()
{
// Change the Text Box Named tbox_Data from here
}
}
}
Can anyone help me please?
You can try something like this.
in your Dosomethingelse() method get a reference to your text box.
public void Dosomethingelse(TextBox textBox,string yourText)
{
var myTextbox = textbox;
mytextbox.text = yourText;
}
now when you are calling the method from MainWindows.xaml.cs call like this,,
myManager.Dosomethingelse(tbox_data,stringYouWantToAdd);
Ok solved!, I found this method from one of iJays' answers.
First you must create a static variable of your MainWindow. So your library can access it. And you must create a string property to set the text of your textbox.Then in the set accessor of that property you can use dispatcher to change the text
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public ProductionManager myManager;
public MainWindow()
{
InitializeComponent();
myManager = new ProductionManager();
window = this;
}
internal static MainWindow window;
public string myString
{
get { return myTextBox.Text; }
//this is where you change the text of your text box
set { Dispatcher.Invoke(new Action(() => { myTextBox.Text = value; })); }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
myManager.DoSomething("Hello world");
}
}
now edit your 'DoSomething()' method as the following,
public void Dosomethingelse(string text)
{
MainWindows.window.myString = text;
}
You can use an event system and the SynchronizationContext class, this way you don't need the extra libraries.
namespace Manager
{
public class ProductionManager
{
private static ProductionManager instance;
// Store the UI context
private readonly SynchronizationContext _UIContext;
// Define event handler signature
public delegate void TextChangedHandler(object sender, TextEventArgs e);
// Define the event to listen for in your MainWindow
public event TextChangedHandler TextChangeEvent;
private ProductionManager()
{
// Set the SynchronizationContext, make sure to call your Instance from the MainWindow
_UIContext = SynchronizationContext.Current;
}
public static ProductionManager Instance
{
get
{
if (instance == null)
{
instance = new ProductionManager();
}
return instance;
}
}
public void DoSomethingElse()
{
// Don't change the TextBox here send the event to the MainWindow instead
// Use the SynchronizationContext to invoke the event
_UIContext.Send(o => TextChangeEvent?.Invoke(this, new TextEventArgs("Hello World!"), null);
}
}
// The TextEventArgs, change for your needs.
public class TextEventArgs : EventArgs
{
public TextEventArgs(string text) : base()
{
Text = text;
}
public string Text { get; }
}
}
Change your MainWindow
public partial class MainWindow : Window
{
private ProductionManager myManager = ProductionManager.Instance;
public MainWindow()
{
InitializeComponent();
// Hookup the event listener
myManager.TextChangeEvent += UpdateTextBox;
}
private void UpdateTextBox(object sender, TextEventArgs e)
{
// Finally handle your text change here
tbox_Data.Text = e.Text;
}
private void btnRun_Click(object sender, RoutedEventArgs e)
{
myManager.DoSomethingElse();
}
}
I have a Button that closes my window when it's clicked:
<Button x:Name="buttonOk" IsCancel="True">Ok</Button>
That's fine until I add a Command to the Button i.e.
<Button x:Name="buttonOk"
Command="{Binding SaveCommand}"
IsCancel="True">Ok</Button>
Now it doesn't close presumably because I am handling the Command. I can fix this by putting an EventHandler in and calling this.Close() i.e.
<Button x:Name="buttonOk"
Click="closeWindow"
Command="{Binding SaveCommand}"
IsCancel="True">Ok</Button>
but now I have code in my code behind i.e. the method SaveCommand. I am using the MVVM pattern and SaveCommand is the only code in my code behind.
How can I do this differently so as not to use code behind?
I just completed a blog post on this very topic. In a nutshell, add an Action property to your ViewModel with get and set accessors. Then define the Action from your View constructor. Finally, invoke your action in the bound command that should close the window.
In the ViewModel:
public Action CloseAction { get; set;}
and in the View constructor:
private View()
{
InitializeComponent();
ViewModel vm = new ViewModel();
this.DataContext = vm;
if ( vm.CloseAction == null )
vm.CloseAction = new Action(this.Close);
}
Finally, in whatever bound command that should close the window, we can simply invoke
CloseAction(); // Calls Close() method of the View
This worked for me, seemed like a fairly elegant solution, and saved me a bunch of coding.
Very clean and MVVM way is to use InteractionTrigger and CallMethodAction defined in Microsoft.Interactivity.Core
You will need to add a new namespace as below
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
You will need the Microsoft.Xmal.Behaviours.Wpf assembly and then the below xaml code will work.
<Button Content="Save" Command="{Binding SaveCommand}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:CallMethodAction MethodName="Close"
TargetObject="{Binding RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType=Window}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
You don't need any code behind or anything else and can also call any other method of Window.
As someone commented, the code I have posted is not MVVM friendly, how about the second solution?
1st, not MVVM solution (I will not delete this as a reference)
XAML:
<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>
ViewModel:
public ICommand OkCommand
{
get
{
if (_okCommand == null)
{
_okCommand = new ActionCommand<Window>(DoOk, CanDoOk);
}
return _okCommand ;
}
}
void DoOk(Window win)
{
// Your Code
win.DialogResult = true;
win.Close();
}
bool CanDoOk(Window win) { return true; }
2nd, probably better solution:
Using attached behaviours
XAML
<Button Content="Ok and Close" Command="{Binding OkCommand}" b:CloseOnClickBehaviour.IsEnabled="True" />
View Model
public ICommand OkCommand
{
get { return _okCommand; }
}
Behaviour Class
Something similar to this:
public static class CloseOnClickBehaviour
{
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached(
"IsEnabled",
typeof(bool),
typeof(CloseOnClickBehaviour),
new PropertyMetadata(false, OnIsEnabledPropertyChanged)
);
public static bool GetIsEnabled(DependencyObject obj)
{
var val = obj.GetValue(IsEnabledProperty);
return (bool)val;
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
static void OnIsEnabledPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
var button = dpo as Button;
if (button == null)
return;
var oldValue = (bool)args.OldValue;
var newValue = (bool)args.NewValue;
if (!oldValue && newValue)
{
button.Click += OnClick;
}
else if (oldValue && !newValue)
{
button.PreviewMouseLeftButtonDown -= OnClick;
}
}
static void OnClick(object sender, RoutedEventArgs e)
{
var button = sender as Button;
if (button == null)
return;
var win = Window.GetWindow(button);
if (win == null)
return;
win.Close();
}
}
I'd personally use a behaviour to do this sort of thing:
public class WindowCloseBehaviour : Behavior<Window>
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(WindowCloseBehaviour));
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(WindowCloseBehaviour));
public static readonly DependencyProperty CloseButtonProperty =
DependencyProperty.Register(
"CloseButton",
typeof(Button),
typeof(WindowCloseBehaviour),
new FrameworkPropertyMetadata(null, OnButtonChanged));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public Button CloseButton
{
get { return (Button)GetValue(CloseButtonProperty); }
set { SetValue(CloseButtonProperty, value); }
}
private static void OnButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var window = (Window)((WindowCloseBehaviour)d).AssociatedObject;
((Button) e.NewValue).Click +=
(s, e1) =>
{
var command = ((WindowCloseBehaviour)d).Command;
var commandParameter = ((WindowCloseBehaviour)d).CommandParameter;
if (command != null)
{
command.Execute(commandParameter);
}
window.Close();
};
}
}
You can then attach this to your Window and Button to do the work:
<Window x:Class="WpfApplication6.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfApplication6"
Title="Window1" Height="300" Width="300">
<i:Interaction.Behaviors>
<local:WindowCloseBehaviour CloseButton="{Binding ElementName=closeButton}"/>
</i:Interaction.Behaviors>
<Grid>
<Button Name="closeButton">Close</Button>
</Grid>
</Window>
I've added Command and CommandParameter here so you can run a command before the Window closes.
For small apps, I use my own Application Controller for showing, closing and disposing windows and DataContexts. It's a central point in UI of an application.
It's something like this:
//It is singleton, I will just post 2 methods and their invocations
public void ShowNewWindow(Window window, object dataContext = null, bool dialog = true)
{
window.DataContext = dataContext;
addToWindowRegistry(dataContext, window);
if (dialog)
window.ShowDialog();
else
window.Show();
}
public void CloseWindow(object dataContextSender)
{
var correspondingWindows = windowRegistry.Where(c => c.DataContext.Equals(dataContextSender)).ToList();
foreach (var pair in correspondingWindows)
{
pair.Window.Close();
}
}
and their invocations from ViewModels:
// Show new Window with DataContext
ApplicationController.Instance.ShowNewWindow(
new ClientCardsWindow(),
new ClientCardsVM(),
false);
// Close Current Window from viewModel
ApplicationController.Instance.CloseWindow(this);
Of course you can find some restrictions in my solution. Again: I use it for small projects, and it's enough. If you're interested, I can post full code here or somewhere else/
I've tried to resolve this issue in some generic, MVVM way, but I always find that I end up unnecessary complex logic. To achieve close behavior I have made an exception from the rule of no code behind and resorted to simply using good ol' events in code behind:
XAML:
<Button Content="Close" Click="OnCloseClicked" />
Code behind:
private void OnCloseClicked(object sender, EventArgs e)
{
Visibility = Visibility.Collapsed;
}
Although I wish this would be better supported using commands/MVVM, I simply think that there is no simpler and more clear solution than using events.
I use the Publish Subscribe pattern for complicated class-dependencies:
ViewModel:
public class ViewModel : ViewModelBase
{
public ViewModel()
{
CloseComand = new DelegateCommand((obj) =>
{
MessageBus.Instance.Publish(Messages.REQUEST_DEPLOYMENT_SETTINGS_CLOSED, null);
});
}
}
Window:
public partial class SomeWindow : Window
{
Subscription _subscription = new Subscription();
public SomeWindow()
{
InitializeComponent();
_subscription.Subscribe(Messages.REQUEST_DEPLOYMENT_SETTINGS_CLOSED, obj =>
{
this.Close();
});
}
}
You can leverage Bizmonger.Patterns to get the MessageBus.
MessageBus
public class MessageBus
{
#region Singleton
static MessageBus _messageBus = null;
private MessageBus() { }
public static MessageBus Instance
{
get
{
if (_messageBus == null)
{
_messageBus = new MessageBus();
}
return _messageBus;
}
}
#endregion
#region Members
List<Observer> _observers = new List<Observer>();
List<Observer> _oneTimeObservers = new List<Observer>();
List<Observer> _waitingSubscribers = new List<Observer>();
List<Observer> _waitingUnsubscribers = new List<Observer>();
int _publishingCount = 0;
#endregion
public void Subscribe(string message, Action<object> response)
{
Subscribe(message, response, _observers);
}
public void SubscribeFirstPublication(string message, Action<object> response)
{
Subscribe(message, response, _oneTimeObservers);
}
public int Unsubscribe(string message, Action<object> response)
{
var observers = new List<Observer>(_observers.Where(o => o.Respond == response).ToList());
observers.AddRange(_waitingSubscribers.Where(o => o.Respond == response));
observers.AddRange(_oneTimeObservers.Where(o => o.Respond == response));
if (_publishingCount == 0)
{
observers.ForEach(o => _observers.Remove(o));
}
else
{
_waitingUnsubscribers.AddRange(observers);
}
return observers.Count;
}
public int Unsubscribe(string subscription)
{
var observers = new List<Observer>(_observers.Where(o => o.Subscription == subscription).ToList());
observers.AddRange(_waitingSubscribers.Where(o => o.Subscription == subscription));
observers.AddRange(_oneTimeObservers.Where(o => o.Subscription == subscription));
if (_publishingCount == 0)
{
observers.ForEach(o => _observers.Remove(o));
}
else
{
_waitingUnsubscribers.AddRange(observers);
}
return observers.Count;
}
public void Publish(string message, object payload)
{
_publishingCount++;
Publish(_observers, message, payload);
Publish(_oneTimeObservers, message, payload);
Publish(_waitingSubscribers, message, payload);
_oneTimeObservers.RemoveAll(o => o.Subscription == message);
_waitingUnsubscribers.Clear();
_publishingCount--;
}
private void Publish(List<Observer> observers, string message, object payload)
{
Debug.Assert(_publishingCount >= 0);
var subscribers = observers.Where(o => o.Subscription.ToLower() == message.ToLower());
foreach (var subscriber in subscribers)
{
subscriber.Respond(payload);
}
}
public IEnumerable<Observer> GetObservers(string subscription)
{
var observers = new List<Observer>(_observers.Where(o => o.Subscription == subscription));
return observers;
}
public void Clear()
{
_observers.Clear();
_oneTimeObservers.Clear();
}
#region Helpers
private void Subscribe(string message, Action<object> response, List<Observer> observers)
{
Debug.Assert(_publishingCount >= 0);
var observer = new Observer() { Subscription = message, Respond = response };
if (_publishingCount == 0)
{
observers.Add(observer);
}
else
{
_waitingSubscribers.Add(observer);
}
}
#endregion
}
}
Subscription
public class Subscription
{
#region Members
List<Observer> _observerList = new List<Observer>();
#endregion
public void Unsubscribe(string subscription)
{
var observers = _observerList.Where(o => o.Subscription == subscription);
foreach (var observer in observers)
{
MessageBus.Instance.Unsubscribe(observer.Subscription, observer.Respond);
}
_observerList.Where(o => o.Subscription == subscription).ToList().ForEach(o => _observerList.Remove(o));
}
public void Subscribe(string subscription, Action<object> response)
{
MessageBus.Instance.Subscribe(subscription, response);
_observerList.Add(new Observer() { Subscription = subscription, Respond = response });
}
public void SubscribeFirstPublication(string subscription, Action<object> response)
{
MessageBus.Instance.SubscribeFirstPublication(subscription, response);
}
}
There is a useful behavior for this task which doesn't break MVVM, a Behavior, introduced with Expression Blend 3, to allow the View to hook into commands defined completely within the ViewModel.
This behavior demonstrates a simple technique for allowing the
ViewModel to manage the closing events of the View in a
Model-View-ViewModel application.
This allows you to hook up a behavior in your View (UserControl) which
will provide control over the control's Window, allowing the ViewModel
to control whether the window can be closed via standard ICommands.
Using Behaviors to Allow the ViewModel to Manage View Lifetime in M-V-VM
http://gallery.expression.microsoft.com/WindowCloseBehavior/
Above link has been archived to http://code.msdn.microsoft.com/Window-Close-Attached-fef26a66#content
I struggled with this topic for some time, and eventually went with the simplest approach that is still consistent with MVVM: Have the button execute the Command that does all the heavy lifting and have the button's Click handler close the window.
XAML
<Button x:Name="buttonOk"
Click="closeWindow"
Command="{Binding SaveCommand}" />
XAML.cs
public void closeWindow()
{
this.DialogResult = true;
}
SaveCommand.cs
// I'm in my own file, not the code-behind!
True, there is still code-behind, but there isn't anything inherently bad about that. And it makes the most sense to me, from an OO perspective, to just tell the window to close itself.
We have the name property in the .xaml definition:
x:Name="WindowsForm"
Then we have the button:
<Button Command="{Binding CloseCommand}"
CommandParameter="{Binding ElementName=WindowsForm}" />
Then in the ViewModel:
public DelegateCommand <Object> CloseCommand { get; private set; }
Constructor for that view model:
this.CloseCommand = new DelegateCommand<object>(this.CloseAction);
Then at last, the action method:
private void CloseAction (object obj)
{
Window Win = obj as Window;
Win.Close();
}
I used this code to close a pop-up window from an application..
I found myself having to do this on a WPF application based on .Net Core 3.0, where unfortunately behaviour support was not yet officially available in the Microsoft.Xaml.Behaviors.Wpf NuGet package.
Instead, I went with a solution that made use of the Façade design pattern.
Interface:
public interface IWindowFacade
{
void Close();
}
Window:
public partial class MainWindow : Window, IWindowFacade
…
Standard command property on the view model:
public ICommand ExitCommand
…
Control binding:
<MenuItem Header="E_xit" Command="{Binding ExitCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>
Command:
public class ExitCommand : ICommand
{
…
public void Execute(object parameter)
{
var windowFacade = parameter as IWindowFacade;
windowFacade?.Close();
}
…
}
Because the Close() method is already implemented by the Window class, applying the façade interface to the window is the only required code behind in the UI layer (for this simple example). The command in the presentation layer avoids any dependencies on the view/UI layer as it has no idea what it is talking to when it calls the Close method on the façade.
In your current window xaml.cs file, call the below code:
var curWnd = Window.GetWindow(this); // passing current window context
curWnd?.Close();
This should do the thing.
It worked for me, hope will do the same for you )
I have following solution in Silverlight. Would also be in WPF.
ChildWindowExt.cs:
namespace System.Windows.Controls
{
public class ChildWindowExt : ChildWindow
{
public static readonly DependencyProperty IsOpenedProperty =
DependencyProperty.Register(
"IsOpened",
typeof(bool),
typeof(ChildWindowExt),
new PropertyMetadata(false, IsOpenedChanged));
private static void IsOpenedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue == false)
{
ChildWindowExt window = d as ChildWindowExt;
window.Close();
}
else if ((bool)e.NewValue == true)
{
ChildWindowExt window = d as ChildWindowExt;
window.Show();
}
}
public bool IsOpened
{
get { return (bool)GetValue(IsOpenedProperty); }
set { SetValue(IsOpenedProperty, value); }
}
protected override void OnClosing(ComponentModel.CancelEventArgs e)
{
this.IsOpened = false;
base.OnClosing(e);
}
protected override void OnOpened()
{
this.IsOpened = true;
base.OnOpened();
}
}
}
ItemWindow.xaml:
<extControls:ChildWindowExt
x:Class="MyProject.ItemWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:extControls="clr-namespace:System.Windows.Controls"
Title="{Binding Title}" IsOpened="{Binding IsOpened, Mode=TwoWay}" Width="640" Height="480">
<Grid x:Name="LayoutRoot">
<Button Command="{Binding UpdateCommand}" Content="OK" Width="70" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</extControls:ChildWindowExt>
ItemViewModel.cs:
private bool _IsOpened;
public bool IsOpened
{
get
{
return _IsOpened;
}
set
{
if (!Equals(_IsOpened, value))
{
_IsOpened = value;
RaisePropertyChanged("IsOpened");
}
}
}
private RelayCommand _UpdateCommand;
/// <summary>
/// Insert / Update data entity
/// </summary>
public RelayCommand UpdateCommand
{
get
{
if (_UpdateCommand == null)
{
_UpdateCommand = new RelayCommand(
() =>
{
// Insert / Update data entity
...
IsOpened = false;
},
() =>
{
return true;
});
}
return _UpdateCommand;
}
}
ItemsViewModel.cs:
private RelayCommand _InsertItemCommand;
/// <summary>
///
/// </summary>
public RelayCommand InsertItemCommand
{
get
{
if (_InsertItemCommand == null)
{
_InsertItemCommand = new RelayCommand(
() =>
{
ItemWindow itemWin = new ItemWindow();
itemWin.DataContext = new ItemViewModel();
itemWin.Show();
// OR
// ItemWindow itemWin = new ItemWindow();
// ItemViewModel newItem = new ItemViewModel();
// itemWin.DataContext = newItem;
// newItem.IsOpened = true;
},
() =>
{
return true;
});
}
return _InsertItemCommand;
}
}
MainPage.xaml:
<Grid x:Name="LayoutRoot">
<Button Command="{Binding InsertItemCommand}" Content="Add New" Width="70" HorizontalAlignment="Left" VerticalAlignment="Center" />
</Grid>
I wish you all good ideas and projects ;-)
This might helps you, closing a wpf window using mvvm with minimal code behind: http://jkshay.com/closing-a-wpf-window-using-mvvm-and-minimal-code-behind/
I think the most simple way has not been included already (almost). Instead of using Behaviours which adds new dependencies just use attached properties:
using System;
using System.Windows;
using System.Windows.Controls;
public class DialogButtonManager
{
public static readonly DependencyProperty IsAcceptButtonProperty = DependencyProperty.RegisterAttached("IsAcceptButton", typeof(bool), typeof(DialogButtonManager), new FrameworkPropertyMetadata(OnIsAcceptButtonPropertyChanged));
public static readonly DependencyProperty IsCancelButtonProperty = DependencyProperty.RegisterAttached("IsCancelButton", typeof(bool), typeof(DialogButtonManager), new FrameworkPropertyMetadata(OnIsCancelButtonPropertyChanged));
public static void SetIsAcceptButton(UIElement element, bool value)
{
element.SetValue(IsAcceptButtonProperty, value);
}
public static bool GetIsAcceptButton(UIElement element)
{
return (bool)element.GetValue(IsAcceptButtonProperty);
}
public static void SetIsCancelButton(UIElement element, bool value)
{
element.SetValue(IsCancelButtonProperty, value);
}
public static bool GetIsCancelButton(UIElement element)
{
return (bool)element.GetValue(IsCancelButtonProperty);
}
private static void OnIsAcceptButtonPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Button button = sender as Button;
if (button != null)
{
if ((bool)e.NewValue)
{
SetAcceptButton(button);
}
else
{
ResetAcceptButton(button);
}
}
}
private static void OnIsCancelButtonPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Button button = sender as Button;
if (button != null)
{
if ((bool)e.NewValue)
{
SetCancelButton(button);
}
else
{
ResetCancelButton(button);
}
}
}
private static void SetAcceptButton(Button button)
{
Window window = Window.GetWindow(button);
button.Command = new RelayCommand(new Action<object>(ExecuteAccept));
button.CommandParameter = window;
}
private static void ResetAcceptButton(Button button)
{
button.Command = null;
button.CommandParameter = null;
}
private static void ExecuteAccept(object buttonWindow)
{
Window window = (Window)buttonWindow;
window.DialogResult = true;
}
private static void SetCancelButton(Button button)
{
Window window = Window.GetWindow(button);
button.Command = new RelayCommand(new Action<object>(ExecuteCancel));
button.CommandParameter = window;
}
private static void ResetCancelButton(Button button)
{
button.Command = null;
button.CommandParameter = null;
}
private static void ExecuteCancel(object buttonWindow)
{
Window window = (Window)buttonWindow;
window.DialogResult = false;
}
}
Then just set it on your dialog buttons:
<UniformGrid Grid.Row="2" Grid.Column="1" Rows="1" Columns="2" Margin="3" >
<Button Content="Accept" IsDefault="True" Padding="3" Margin="3,0,3,0" DialogButtonManager.IsAcceptButton="True" />
<Button Content="Cancel" IsCancel="True" Padding="3" Margin="3,0,3,0" DialogButtonManager.IsCancelButton="True" />
</UniformGrid>
I also had to deal with this problem, so here my solution. It works great for me.
1. Create class DelegateCommand
public class DelegateCommand<T> : ICommand
{
private Predicate<T> _canExecuteMethod;
private readonly Action<T> _executeMethod;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<T> executeMethod) : this(executeMethod, null)
{
}
public DelegateCommand(Action<T> executeMethod, Predicate<T> canExecuteMethod)
{
this._canExecuteMethod = canExecuteMethod;
this._executeMethod = executeMethod ?? throw new ArgumentNullException(nameof(executeMethod), "Command is not specified.");
}
public void RaiseCanExecuteChanged()
{
if (this.CanExecuteChanged != null)
CanExecuteChanged(this, null);
}
public bool CanExecute(object parameter)
{
return _canExecuteMethod == null || _canExecuteMethod((T)parameter) == true;
}
public void Execute(object parameter)
{
_executeMethod((T)parameter);
}
}
2. Define your command
public DelegateCommand<Window> CloseWindowCommand { get; private set; }
public MyViewModel()//ctor of your viewmodel
{
//do something
CloseWindowCommand = new DelegateCommand<Window>(CloseWindow);
}
public void CloseWindow(Window win) // this method is also in your viewmodel
{
//do something
win?.Close();
}
3. Bind your command in the view
public MyView(Window win) //ctor of your view, window as parameter
{
InitializeComponent();
MyButton.CommandParameter = win;
MyButton.Command = ((MyViewModel)this.DataContext).CloseWindowCommand;
}
4. And now the window
Window win = new Window()
{
Title = "My Window",
Height = 800,
Width = 800,
WindowStartupLocation = WindowStartupLocation.CenterScreen,
};
win.Content = new MyView(win);
win.ShowDialog();
so thats it, you can also bind the command in the xaml file and find the window with FindAncestor and bind it to the command parameter.
I've been searching for a solution to the same problem and found that doing following works fine. The solution is similar to what OP has mentioned in his question with some differences:
No need of IsCancel property.
Code behind should not close window. Just set DialogResult
In my case it first executes code behind and then view model command bound to the button.
XAML
<Button x:Name="buttonOk" Click="Save_Click" Command="{Binding SaveCommand}">OK</Button>
Code Behind
private void Apply_OnClick(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
}
View Model
private void Save()
{
// Save data.
}
Hope this helps.
You could rephrase the question, and by doing so - coming up with another solution.
How can I enable communication between views, viewmodels and whatnot in an MVVM environment?
You could use the Mediator pattern. It's basically a notification system. For the actual Mediator implementation, google for it or ask me and I can email it.
Make a Command whose purpose is to close the view.
public void Execute( object parameter )
{
this.viewModel.DisposeMyStuff();
Mediator.NotifyColleagues(Mediator.Token.ConfigWindowShouldClose);
}
The Mediator will raise a notification (a token)
Listen to this notification (token) like this in the View codebehind constructor:
public ClientConfigView()
{
InitializeComponent();
Mediator.ListenOn(Mediator.Token.ConfigWindowShouldClose, callback => this.Close() );
}
The solution to close a window in wpf that that worked for me is not answered here so i thought i would add my solution too.
private static Window GetWindow(DependencyObject sender)
{
Window window = null;
if (sender is Window)
window = (Window)sender;
if (window == null)
window = Window.GetWindow(sender);
return window;
}
private void CloseWindow(object sender, RoutedEventArgs e)
{
var button = (Button)sender as DependencyObject;
Window window = GetWindow(button);
if (window != null)
window.Close();
// window.Visibility = Visibility.Hidden;
// choose between window.close or set window.visibility to close or hide the window.
// }
}
Add CloseWindow event to the button in you window as following.
<Button Content="Cancel" Click="CloseWindow" >
Simple approach is close window on saveComand Implementation.
Use below code to close window.
Application.Current.Windows[1].Close();
It will close the child window.
Without any dependencies.
<Window ...>
...
<Button Command="{x:Static SystemCommands.CloseWindowCommand}" />
</Window>
You can do it without code behind. Create command, in Execute method call "Save" method on viewmodel and after that call close method on edit window, which you can pass to the command by parameter:
public void Execute(object parameter)
{
_mainViewModel.SaveSomething();
var editWindow = parameter as MyEditWindow;
editWindow?.Close();
}
Save&Close button XAML:
<Button Content"Save&Close" Command="{Binding SaveCmd}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" IsDefault="True" />