ViewModel doesn't bind to View MvvmCross - c#

I have the following problem:
in the application, when the user returns to the computer, I have to process the event and show him a modal window in which to offer 3 possible answers.
I do this using the service:
public WinUserReturnDialogServiceImpl(ISettingsManager
settingsManager) : base(settingsManager)
{
_dialogPage = new UserReturnDialogPage();
_dialogPage.AddButton.Click += OnAddButtonClick;
_dialogPage.DivideButton.Click += OnDivideButtonClick;
_dialogPage.CancelButton.Click += OnCancelButtonClick;
_dialogWindow = new DialogWindow()
{
Content = _dialogPage
};
}
protected override void ShowCustomDialog(UserReturnDialogData dialogData)
{
_pauseDuration = Math.Floor(dialogData._userAfkMinuites);
Application.Current.Dispatcher.Invoke(() =>
{
_dialogPage.AFKMessage.Text = string.Format("Вы отсутствовали {0} мин", _pauseDuration);
_dialogWindow.Show();
});
return;
}
private void OnAddButtonClick(object sender, RoutedEventArgs e)
{
CloseCustomDialog();
}
Page Code behind:
//[MvxViewFor(typeof(UserReturnDialogViewModel))]
//[WinContentPresentation(IsSheet = true, TransitionForwardType = TransitionType.ToRight, TransitionReturnType = TransitionType.FromRigth, WindowIdentifier = nameof(DialogWindow))]
[MvxContentPresentation(WindowIdentifier = nameof(DialogWindow), StackNavigation = false)]
public partial class UserReturnDialogPage : MvxWpfView<UserReturnDialogViewModel>
{
public UserReturnDialogPage()
{
InitializeComponent();
}
and page xaml:
<views:MvxWpfView
x:Class="SmlHours.Win.Presentation.Views.Pages.Dialogs.UserReturnDialogPage"
x:TypeArguments="vm:UserReturnDialogViewModel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
xmlns:vm="clr-namespace:SmlHours.Core.Presentation.ViewModels.Dialogs;assembly=SmlHours.Core"
...
<Button
x:Name="AddButton"
HorizontalAlignment="Left"
Visibility="Visible"
Width="85"
Height="32"
Template="{DynamicResource BaseNavigationButtonTemplate}"
Command="{Binding AddTimeAfterUserReturn}"
ViewModel:
public class UserReturnDialogViewModel : BaseViewModel
{
private IMonitoringInteractor _monitoringInteractor;
public IMvxCommand AddTimeAfterUserReturn { get; private set; }
public UserReturnDialogViewModel(IMonitoringInteractor monitoringInteractor)
{
_monitoringInteractor = monitoringInteractor;
AddTimeAfterUserReturn = CreateAsyncCommand(AddTimeAfterReturnTask);
}
//I need to fire this command after button pressed!!!
private Task AddTimeAfterReturnTask() => Task.Run(async () =>
{
var model = _monitoringInteractor.FixationTimeAfterUserReturn();
});
}
The event is triggered, the page is displayed, but the viewmodel is not attached to the page and does not respond to button presses. However, the OnAddButtonClick, etc. service commands that close the window react to pressing the buttons.
How do I bind view and viewmodel, so that pressing the buttons works in the viewmodel?
Thanks a lot to those who responded

Problem was that I try to create new Window/Page by manual. For binding vm and view should use ViewDispatcher:
var ViewDispatcher = Mvx.Resolve<IMvxViewDispatcher>();
ViewDispatcher.ShowViewModel(newMvxViewModelRequest(typeof(UserReturnDialogViewModel)));

Related

How to delete data in a .NET MAUI list

I am currently doing a project with the MVVM method in NET MAUI to add, modify and delete drivers.
I have a template that contains the name, first name and number of points of the driver.
Then I have two views each with a model view:
- One that represents the list of my drivers with the possibility to add a driver, to select a driver from the list by going to another page (PageListPilotViewModel).
- And another one which represents the selected driver in another page to be able to modify its data and the possibility of removing it. (ProfilePilotViewModel)
At the moment I can select, add the driver and modify the driver in the other page. But I can't delete the driver in the profile page.
Here is what I have done so far:
-> Models : Pilote Model
public class PiloteModel : INotifyPropertyChanged
{
private string _nom;
public string Nom
{
get { return _nom; }
set { _nom = value; OnPropertyChanged(); }
}
private string _prenom;
public string Prenom
{
get { return _prenom; }
set { _prenom = value; OnPropertyChanged(); }
}
private int _points;
public int Points
{
get { return _points; }
set { _points = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
-> View : ProfilPilotePage
<vm:PageListPiloteViewModel></vm:PageListPiloteViewModel>
</ContentPage.BindingContext>
<VerticalStackLayout>
<StackLayout>
<Entry Text="{Binding Pilote.Nom, Mode=TwoWay}" Placeholder="{Binding Nom}"></Entry>
<Entry Text="{Binding Pilote.Prenom}" Placeholder="{Binding Pilote.Prenom}"></Entry>
<Entry Text="{Binding Pilote.Points}" Placeholder="{Binding Pilote.Points}"></Entry>
<Button Command="{Binding OnsupprimerPiloteCommand}">
</Button>
</StackLayout>
-> code behind the profilePilotPage view
public partial class ProfilPilotePage : ContentPage
{
private PageListPiloteViewModel _viewModel;
public ProfilPilotePage(PageListPiloteViewModel viewModel)
{
InitializeComponent();
_viewModel = viewModel;
_viewModel.SupprimerPiloteClicked += OnSupprimerPiloteClicked;
BindingContext = _viewModel;
}
private void OnSupprimerPiloteClicked(object sender, PiloteModel e)
{
_viewModel.ListePilotes.Remove(e);
}
-> model views : PageListPilotViewModel , to be able to delete also the driver in the list
public ICommand OnsupprimerPiloteCommand { get; set; }
public PageListPiloteViewModel()
{
ValiderCommand = new Command(AjouterPilote);
OnsupprimerPiloteCommand = new Command(OnSupprimerPiloteClicked);
SelectedPilote = new PiloteModel();
ListePilotes = new ObservableCollection<Models.PiloteModel>();
ListePilotes.Add(new Models.PiloteModel { Nom = "Fabio", Prenom = "Quartaro", Points = 215 });
}
private void OnSupprimerPiloteClicked()
{
SupprimerPiloteClicked?.Invoke(this, SelectedPilote);
}
->code behind the PageListPiloteView: with the error I encounter on the last :
await Navigation.PushAsync(new ProfilePilotPage{ BindingContext = viewModel }) :
CS7036 Error None of the specified arguments match the 'viewModel' mandatory parameter of 'ProfilePilotPage.ProfilePilotPage(PageListPilotViewModel)'
private async void SelectionnerPilote(object sender, SelectionChangedEventArgs e)
{
PiloteModel selectedPilote = (PiloteModel)((CollectionView)sender).SelectedItem;
ProfilPiloteViewModel viewModel = new ProfilPiloteViewModel();
viewModel.Pilote = selectedPilote;
await Navigation.PushAsync(new ProfilPilotePage{ BindingContext = viewModel });
}
}
Do you have any idea how to make the specified arguments mandatory please ?
You've mixed up constructor and initializer.
This line
await Navigation.PushAsync(new ProfilPilotePage{ BindingContext = viewModel });
should be
await Navigation.PushAsync(new ProfilPilotePage(viewModel));
The reason for this is that you're defining an argument in the signature of the ProfilPilotePage's constructor:
public ProfilPilotePage(PageListPiloteViewModel viewModel)
{
//...
}
Therefore, you must pass the ViewModel argument.
At first, you can try to use the ewerspej's solution or only add a default construction method without any parameter into the ProfilPilotePage to fix the error caused by await Navigation.PushAsync(new ProfilePilotPage{ BindingContext = viewModel }) . Such as:
public partial class ProfilPilotePage : ContentPage
{
private PageListPiloteViewModel _viewModel;
public ProfilPilotePage()
{
InitializeComponent();
}
public ProfilPilotePage(PageListPiloteViewModel viewModel)
{
InitializeComponent();
_viewModel = viewModel;
_viewModel.SupprimerPiloteClicked += OnSupprimerPiloteClicked;
BindingContext = _viewModel;
}
}
And then I saw you used both the mvvm and the code behind. You can remove the OnSupprimerPiloteClicked(object sender, PiloteModel e) in the page.cs and change the OnSupprimerPiloteClicked() in the view model. Such as:
private void OnSupprimerPiloteClicked()
{
ListePilotes.Remove(SelectedPilote);
}
Finally, I saw SelectedPilote = new PiloteModel(); in your viewmodel. Which item in the list did you want to delete? I think it should be the seleted item not a new PiloteModel().

ObservableCollection showing up in UI, but slow in Xamarin

There is a page where the user selects parameters to show the proper collection then on button click jumps to the next page (Coll) where it should show up.
User Selection Page XAML:
<ContentPage.BindingContext><xyz:UserSelectionViewModel</ContentPage.BindingContext>
...
<Button x:Name="Start" Command="{Binding LoadData}" Pressed="StartClick"/>
User Selection Page C#:
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (CollViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
...
vm.Subject = vm.Subject.ToLower();
}
UserSelectionViewModel:
public class UserSelectionViewModel : BaseViewModel
{
public UserSelectionViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
_pageService = DependencyService.Get<IPageService>();
LoadData= new AsyncAwaitBestPractices.MVVM.AsyncCommand(FilterData);
FilteredData = new ObservableRangeCollection<Items>();
}
public async Task FilterData()
{
FilteredData.Clear();
var filtereddata = await _dataStore.SearchData(Hard, Subject).ConfigureAwait(false);
FilteredData.AddRange(filtereddata);
OnPropertyChanged("FilteredData");
Debug.WriteLine(FilteredData.Count());
await Device.InvokeOnMainThreadAsync(() => _pageService.PushAsync(new Coll(FilteredData)));
}
}
Coll XAML:
<ContentPage.BindingContext><xyz:CollViewModel</ContentPage.BindingContext>
...
<CarouselView ItemsSource="{Binding Source={RelativeSource AncestorType={x:Type z:Coll}}, Path=InheritedData}" ItemTemplate="{StaticResource CollTemplateSelector}">
...
Coll C#:
public partial class Coll : ContentPage
{
public ObservableRangeCollection<Feladatok> InheritedData { get; set; }
public Coll(ObservableRangeCollection<Feladatok> x)
{
InitializeComponent();
InheritedData = x;
OnPropertyChanged(nameof(InheritedData));
}
}
CollViewModel:
public class CollViewModel : UserSelectionViewModel { ... }
BaseViewModel:
private ObservableRangeCollection<Feladatok> inheriteddata;
public ObservableRangeCollection<Feladatok> InheritedData
{
get
{
return inheriteddata;
}
set
{
if (value != inheriteddata)
{
inheriteddata = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("InheritedData"));
}
}
}
Managed to make it work like this with the help of Jason's tips. My only concern remaining is that: Won't this slow down the page that I load the observable collection two times basically? Is it a good practice as I have made it?
Eventually set the BindingContext to the VM and Binding from there. I still feel like it could be done more efficently or maybe that's how it is done. ViewModels are still new for me and I feel like it's much more code and slower with it. But I will close this, as it is working now.

Values of View don't update after changing data

I'm trying to build an application that collects information of dams and weather forecasts from api and shows the data to users.
As shown below, the main page is supposed to look similar for dam information and weather forcast information.
Blue print
So, I want to make a component for card, that I can reuse for the data. In other words, I want to put 1) mainCards into 2)mainPage, so that I can have a page full of cards. And then, I want to put the mainPage on top of 3)mainWindow.
I want to change the values inside of the purple cards when I click a menu button on the left side that says "Dam Info" and "Weather Info".
When I press the "Dam Info" button, I want the purple cards to fetch dam data from an API and show it on the screen. Same for "Weather Info" button.
The problem is, I can clearly see that the values that I binded to the text blocks in the cards change when I run the code and show them on console. But, the actual UI doesn't update the change.
INotifyProperty says that the view model I implement for the current view changes as I press a button. It also says that the values(that I binded to the textblocks for purple cards) in a view model for purple card components change when I press a button.
I implemented a base class that implemented INotifyPropertyChanged for all view models, and RelayCommand for all commands.
I assumed that with INotifyPropertyChanged, the view will automatically change the UI value, but it does not.
I already tried to set the mode for binding to Two Way , but it doesn't work.
I tried to set certain values in a view model for Main Page(page that contains a lot of purple card components) and then make the purple card view model to fetch and reflect it. But it doesn't work.
Please help me. I've been dealing with this problem for a week. I have not much time left to just struggle with changing views and their components. I'm new to MVVM pattern and WPF and completely lost.
xaml of Main window is shown below
<Grid>
<RadioButton Content="Dam Info"
Command="{Binding ChangeMainPageForDamCommand}"
CommandParameter="Dam"/>
<RadioButton Content="Weather Info"
Command="{Binding ChangeMainPageForWeatherCommand}"
CommandParameter="Weather"/>
<RadioButton Content="My data"
Command="{Binding DetailPageCommand}"/>
<ContentControl Content="{Binding CurrentView,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
view model for main window is shown below
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged();
}
}
public RelayCommand MainPageCommand { get; set; }
public RelayCommand DetailPageCommand { get; set; }
public RelayCommand ChangeMainPageForDamCommand { get; set; }
public RelayCommand ChangeMainPageForWeatherCommand { get; set; }
public MainPageViewModel DamMainPageVM { get; set; }
public MainPageViewModel WeatherMainPageVM { get; set; }
public MainPageViewModel MainPageVM { get; set; }
public DetailPageViewModel DetailPageVM { get; set; }
public MainWindowViewModel()
{
MainPageVM = new MainPageViewModel("Dam");
CurrentView = MainPageVM;
DetailPageVM = new DetailPageViewModel();
ChangeMainPageForDamCommand = new RelayCommand(o =>
{
CurrentView = null;
DamMainPageVM = new MainPageViewModel("Dam");
CurrentView = DamMainPageVM;
Debug.WriteLine("ChangeMainPageFor Dam Command executed");
});
ChangeMainPageForWeatherCommand = new RelayCommand(o =>
{
WeatherMainPageVM = new MainPageViewModel("Weather");
CurrentView = WeatherMainPageVM;
Debug.WriteLine("ChangeMainPageFor Weather Command executed");
});
DetailPageCommand = new RelayCommand(o =>
{
CurrentView = DetailPageVM;
});
}
xaml for mainPage that contains cards is shown below
<Grid>
<components:MainCard/>
<components:MainCard/>
<components:MainCard/>
<components:MainCard/>
<components:MainCard/>
<components:MainCard/>
</Grid>
view model for the main page is shown below
public MainCardDatum MainCardDatumItem = new MainCardDatum();
public RelayCommand GetDamDetailCommand { get; set; }
private MainCardViewModel _mainCardViewModel;
public MainCardViewModel MainCardViewModel
{
get
{
return _mainCardViewModel;
}
set
{
_mainCardViewModel = value;
OnPropertyChanged(nameof(MainCardViewModel));
}
}
public MainPageViewModel() { }
public MainPageViewModel(string pageName)
{
var d = new MainCardViewModel(pageName);
GetMainPage(pageName);
Debug.WriteLine(pageName+" is ON");
}
public void GetMainPage(string pageName)
{
GenerateMainCard(pageName);
Debug.WriteLine(pageName+" made the page.");
}
private void GenerateMainCard(string pageName)
{
if (pageName == "Dam")
{
MainCardDatumItem.Id = 1;
MainCardDatumItem.MainCardName = "damDummyInfoName";
MainCardDatumItem.MainCardOption = "damDummyInfoOption";
}
else if (pageName == "Weather")
{
MainCardDatumItem.Id = 1;
MainCardDatumItem.MainCardName = "weatherDummyInfoName";
MainCardDatumItem.MainCardOption = "weatherDummyInfoOption";
}
else
{
Debug.Write("What?");
}
}
Main Card(Purple card component) xaml is below
<Grid>
<StackPanel>
<TextBlock Name="nameHehe"
Text="{Binding MainCardName,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Name="optionHehe"
Text="{Binding MainCardOption}"/>
</StackPanel>
<Button Background="Transparent"
Command="{Binding GetDamDetailCommand}"/>
</Grid>
Finally, the view model for the main card(purple card component) is below
public MainPageViewModel MainPageVM = new MainPageViewModel();
private string _mainCardName;
public string MainCardName
{
get
{
return _mainCardName;
}
set
{
_mainCardName = value;
OnPropertyChanged(nameof(MainCardName));
}
}
private string _mainCardOption;
public string MainCardOption
{
get
{
return _mainCardOption;
}
set
{
_mainCardOption = value;
OnPropertyChanged(nameof(MainCardOption));
}
}
public RelayCommand ClickCommand { get; set; }
public RelayCommand GetDamDetailCommand { get; set; }
public RelayCommand GetWeatherDetailCommand { get; set; }
public MainCardViewModel()
{
GenerateCardsForDam();
GetDamDetailCommand = new RelayCommand(o =>
{
MainCardName = "changed1";
MainCardOption = "changed2";
});
}
public MainCardViewModel(string pageName)
{
if (pageName == "Dam")
GenerateCardsForDam();
else
GenerateCardsForWeather();
GetDamDetailCommand = new RelayCommand(o =>
{
GenerateCardsForWeather();
});
}
public void GenerateCardsForDam()
{
MainCardName = "DamName1";
MainCardOption = "DamOption1";
Debug.WriteLine("GenerateCardsForDam executed");
}
public void GenerateCardsForWeather()
{
MainCardName = "WeatherName1";
MainCardOption = "WeatherOption1";
Debug.WriteLine("GenerateCardsForWeather executed");
}
I deleted out all unnecessary codes that are not related to this problem in xaml.
What am I doing wrong? Please help.
Also, it would be wonderful to know any discord server that I can ask and answer questions related to programming. I'm having a hard time to explain this problem in text tbh. Please let me know if you know one.
Thank you so much for reading so far. Sorry for messy explanation.
https://github.com/yk170901/dnw.git
Above is my Github repository for the project. I tried to add it correctly, but somehow the project folder become empty when I try to upload it. So I added a zip, I hope you can see the problem with the zip file. Sorry for the inconvenience.
You defined a DataTemplate for MainPage (a UserControl) in App.xaml.
<DataTemplate DataType="{x:Type viewModel:MainPageViewModel}">
<view:MainPage/>
</DataTemplate>
When this DataTemplate is applied, it is expected to instantiate a MainPage and set the instance of MainPageViewModel to MainPage's DataContext.
However, you added a code block shown below in MainPage.xaml.
<UserControl.DataContext>
<viewModels:MainPageViewModel/>
</UserControl.DataContext>
This means that when a MainPage is instantiated, an instance of MainPageViewModel will be instantiated as well and that instance will be set to MainPage's DataContext. This seems to prevent the DataTemplate from working as expected.
So, the solution is to remove that code block in MainPage.xaml.
In addition, I found that the appearace of MainPage will be the same regardless of the instance of MainPageViewModel and so you will not be able to check the difference from its appearance.

Close Window from ViewModel [duplicate]

This question already has answers here:
How should the ViewModel close the form?
(25 answers)
Closed 1 year ago.
Im creating a Login using a window control to allow a user to login into a WPF application that I am creating.
So far, I have created a method that checks whether the user has entered in the correct credentials for the username and password in a textbox on the login screen, binding two properties.
I have achieved this by creating a bool method, like so;
public bool CheckLogin()
{
var user = context.Users.Where(i => i.Username == this.Username).SingleOrDefault();
if (user == null)
{
MessageBox.Show("Unable to Login, incorrect credentials.");
return false;
}
else if (this.Username == user.Username || this.Password.ToString() == user.Password)
{
MessageBox.Show("Welcome " + user.Username + ", you have successfully logged in.");
return true;
}
else
{
MessageBox.Show("Unable to Login, incorrect credentials.");
return false;
}
}
public ICommand ShowLoginCommand
{
get
{
if (this.showLoginCommand == null)
{
this.showLoginCommand = new RelayCommand(this.LoginExecute, null);
}
return this.showLoginCommand;
}
}
private void LoginExecute()
{
this.CheckLogin();
}
I also have a command that I bind to my button within the xaml like so;
<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" />
When I enter in the username and password it executes the appropriated code, whether it being right, or wrong. But how can I close this window from the ViewModel when both username and password are correct?
I have previously tried using a dialog modal but it didn't quite work out. Furthermore, within my app.xaml, I have done something like the following, which loads the login page first, then once true, loads the actual application.
private void ApplicationStart(object sender, StartupEventArgs e)
{
Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
var dialog = new UserView();
if (dialog.ShowDialog() == true)
{
var mainWindow = new MainWindow();
Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
Current.MainWindow = mainWindow;
mainWindow.Show();
}
else
{
MessageBox.Show("Unable to load application.", "Error", MessageBoxButton.OK);
Current.Shutdown(-1);
}
}
Question: How can I close the Login Window control from the ViewModel?
Thanks in advance.
You can pass the window to your ViewModel using the CommandParameter. See my Example below.
I've implemented an CloseWindow Method which takes a Windows as parameter and closes it. The window is passed to the ViewModel via CommandParameter. Note that you need to define an x:Name for the window which should be close. In my XAML Window i call this method via Command and pass the window itself as a parameter to the ViewModel using CommandParameter.
Command="{Binding CloseWindowCommand, Mode=OneWay}"
CommandParameter="{Binding ElementName=TestWindow}"
ViewModel
public RelayCommand<Window> CloseWindowCommand { get; private set; }
public MainViewModel()
{
this.CloseWindowCommand = new RelayCommand<Window>(this.CloseWindow);
}
private void CloseWindow(Window window)
{
if (window != null)
{
window.Close();
}
}
View
<Window x:Class="ClientLibTestTool.ErrorView"
x:Name="TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:localization="clr-namespace:ClientLibTestTool.ViewLanguages"
DataContext="{Binding Main, Source={StaticResource Locator}}"
Title="{x:Static localization:localization.HeaderErrorView}"
Height="600" Width="800"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen">
<Grid>
<Button Content="{x:Static localization:localization.ButtonClose}"
Height="30"
Width="100"
Margin="0,0,10,10"
IsCancel="True"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Command="{Binding CloseWindowCommand, Mode=OneWay}"
CommandParameter="{Binding ElementName=TestWindow}"/>
</Grid>
</Window>
Note that i'm using the MVVM light framework, but the principal applies to every wpf application.
This solution violates of the MVVM pattern, because the view-model shouldn't know anything about the UI Implementation. If you want to strictly follow the MVVM programming paradigm you have to abstract the type of the view with an interface.
MVVM conform solution (Former EDIT2)
the user Crono mentions a valid point in the comment section:
Passing the Window object to the view model breaks the MVVM pattern
IMHO, because it forces your vm to know what it's being viewed in.
You can fix this by introducing an interface containing a close method.
Interface:
public interface ICloseable
{
void Close();
}
Your refactored ViewModel will look like this:
ViewModel
public RelayCommand<ICloseable> CloseWindowCommand { get; private set; }
public MainViewModel()
{
this.CloseWindowCommand = new RelayCommand<IClosable>(this.CloseWindow);
}
private void CloseWindow(ICloseable window)
{
if (window != null)
{
window.Close();
}
}
You have to reference and implement the ICloseable interface in your view
View (Code behind)
public partial class MainWindow : Window, ICloseable
{
public MainWindow()
{
InitializeComponent();
}
}
Answer to the original question: (former EDIT1)
Your Login Button (Added CommandParameter):
<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" CommandParameter="{Binding ElementName=LoginWindow}"/>
Your code:
public RelayCommand<Window> CloseWindowCommand { get; private set; } // the <Window> is important for your solution!
public MainViewModel()
{
//initialize the CloseWindowCommand. Again, mind the <Window>
//you don't have to do this in your constructor but it is good practice, thought
this.CloseWindowCommand = new RelayCommand<Window>(this.CloseWindow);
}
public bool CheckLogin(Window loginWindow) //Added loginWindow Parameter
{
var user = context.Users.Where(i => i.Username == this.Username).SingleOrDefault();
if (user == null)
{
MessageBox.Show("Unable to Login, incorrect credentials.");
return false;
}
else if (this.Username == user.Username || this.Password.ToString() == user.Password)
{
MessageBox.Show("Welcome "+ user.Username + ", you have successfully logged in.");
this.CloseWindow(loginWindow); //Added call to CloseWindow Method
return true;
}
else
{
MessageBox.Show("Unable to Login, incorrect credentials.");
return false;
}
}
//Added CloseWindow Method
private void CloseWindow(Window window)
{
if (window != null)
{
window.Close();
}
}
I usually put an event on the view model when I need to do this and then hook it up to the Window.Close() when binding the view model to the window
public class LoginViewModel
{
public event EventHandler OnRequestClose;
private void Login()
{
// Login logic here
OnRequestClose(this, new EventArgs());
}
}
And when creating the login window
var vm = new LoginViewModel();
var loginWindow = new LoginWindow
{
DataContext = vm
};
vm.OnRequestClose += (s, e) => loginWindow.Close();
loginWindow.ShowDialog();
Staying MVVM, I think using either Behaviors from the Blend SDK (System.Windows.Interactivity) or a custom interaction request from Prism could work really well for this sort of situation.
If going the Behavior route, here's the general idea:
public class CloseWindowBehavior : Behavior<Window>
{
public bool CloseTrigger
{
get { return (bool)GetValue(CloseTriggerProperty); }
set { SetValue(CloseTriggerProperty, value); }
}
public static readonly DependencyProperty CloseTriggerProperty =
DependencyProperty.Register("CloseTrigger", typeof(bool), typeof(CloseWindowBehavior), new PropertyMetadata(false, OnCloseTriggerChanged));
private static void OnCloseTriggerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = d as CloseWindowBehavior;
if (behavior != null)
{
behavior.OnCloseTriggerChanged();
}
}
private void OnCloseTriggerChanged()
{
// when closetrigger is true, close the window
if (this.CloseTrigger)
{
this.AssociatedObject.Close();
}
}
}
Then in your window, you would just bind the CloseTrigger to a boolean value that would be set when you wanted the window to close.
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:TestApp"
Title="MainWindow" Height="350" Width="525">
<i:Interaction.Behaviors>
<local:CloseWindowBehavior CloseTrigger="{Binding CloseTrigger}" />
</i:Interaction.Behaviors>
<Grid>
</Grid>
</Window>
Finally, your DataContext/ViewModel would have a property that you'd set when you wanted the window to close like this:
public class MainWindowViewModel : INotifyPropertyChanged
{
private bool closeTrigger;
/// <summary>
/// Gets or Sets if the main window should be closed
/// </summary>
public bool CloseTrigger
{
get { return this.closeTrigger; }
set
{
this.closeTrigger = value;
RaisePropertyChanged(nameof(CloseTrigger));
}
}
public MainWindowViewModel()
{
// just setting for example, close the window
CloseTrigger = true;
}
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
(set your Window.DataContext = new MainWindowViewModel())
it may be late, but here is my answer
foreach (Window item in Application.Current.Windows)
{
if (item.DataContext == this) item.Close();
}
Well here is something I used in several projects. It may look like a hack, but it works fine.
public class AttachedProperties : DependencyObject //adds a bindable DialogResult to window
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached("DialogResult", typeof(bool?), typeof(AttachedProperties),
new PropertyMetaData(default(bool?), OnDialogResultChanged));
public bool? DialogResult
{
get { return (bool?)GetValue(DialogResultProperty); }
set { SetValue(DialogResultProperty, value); }
}
private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window == null)
return;
window.DialogResult = (bool?)e.NewValue;
}
}
Now you can bind DialogResult to a VM and set its value of a property. The Window will close, when the value is set.
<!-- Assuming that the VM is bound to the DataContext and the bound VM has a property DialogResult -->
<Window someNs:AttachedProperties.DialogResult={Binding DialogResult} />
This is an abstract of what's running in our production environment
<Window x:Class="AC.Frontend.Controls.DialogControl.Dialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:DialogControl="clr-namespace:AC.Frontend.Controls.DialogControl"
xmlns:hlp="clr-namespace:AC.Frontend.Helper"
MinHeight="150" MinWidth="300" ResizeMode="NoResize" SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterScreen" Title="{Binding Title}"
hlp:AttachedProperties.DialogResult="{Binding DialogResult}" WindowStyle="ToolWindow" ShowInTaskbar="True"
Language="{Binding UiCulture, Source={StaticResource Strings}}">
<!-- A lot more stuff here -->
</Window>
As you can see, I'm declaring the namespace xmlns:hlp="clr-namespace:AC.Frontend.Helper" first and afterwards the binding hlp:AttachedProperties.DialogResult="{Binding DialogResult}".
The AttachedProperty looks like this. It's not the same I posted yesterday, but IMHO it shouldn't have any effect.
public class AttachedProperties
{
#region DialogResult
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached("DialogResult", typeof (bool?), typeof (AttachedProperties), new PropertyMetadata(default(bool?), OnDialogResultChanged));
private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var wnd = d as Window;
if (wnd == null)
return;
wnd.DialogResult = (bool?) e.NewValue;
}
public static bool? GetDialogResult(DependencyObject dp)
{
if (dp == null) throw new ArgumentNullException("dp");
return (bool?)dp.GetValue(DialogResultProperty);
}
public static void SetDialogResult(DependencyObject dp, object value)
{
if (dp == null) throw new ArgumentNullException("dp");
dp.SetValue(DialogResultProperty, value);
}
#endregion
}
Easy way
public interface IRequireViewIdentification
{
Guid ViewID { get; }
}
Implement to ViewModel
public class MyViewVM : IRequireViewIdentification
{
private Guid _viewId;
public Guid ViewID
{
get { return _viewId; }
}
public MyViewVM()
{
_viewId = Guid.NewGuid();
}
}
Add general window manager helper
public static class WindowManager
{
public static void CloseWindow(Guid id)
{
foreach (Window window in Application.Current.Windows)
{
var w_id = window.DataContext as IRequireViewIdentification;
if (w_id != null && w_id.ViewID.Equals(id))
{
window.Close();
}
}
}
}
And close it like this in viewmodel
WindowManager.CloseWindow(ViewID);
How about this ?
ViewModel:
class ViewModel
{
public Action CloseAction { get; set; }
private void Stuff()
{
// Do Stuff
CloseAction(); // closes the window
}
}
In your ViewModel use CloseAction() to close the window just like in the example above.
View:
public View()
{
InitializeComponent();
ViewModel vm = new ViewModel (); // this creates an instance of the ViewModel
this.DataContext = vm; // this sets the newly created ViewModel as the DataContext for the View
if (vm.CloseAction == null)
vm.CloseAction = new Action(() => this.Close());
}
I know this is an old post, probably no one would scroll this far, I know I didn't. So, after hours of trying different stuff, I found this blog and dude killed it. Simplest way to do this, tried it and it works like a charm.
Blog
In the ViewModel:
...
public bool CanClose { get; set; }
private RelayCommand closeCommand;
public ICommand CloseCommand
{
get
{
if(closeCommand == null)
(
closeCommand = new RelayCommand(param => Close(), param => CanClose);
)
}
}
public void Close()
{
this.Close();
}
...
add an Action property to the ViewModel, but define it from the View’s code-behind file. This will let us dynamically define a reference on the ViewModel that points to the View.
On the ViewModel, we’ll simply add:
public Action CloseAction { get; set; }
And on the View, we’ll define it as such:
public View()
{
InitializeComponent() // this draws the View
ViewModel vm = new ViewModel(); // this creates an instance of the ViewModel
this.DataContext = vm; // this sets the newly created ViewModel as the DataContext for the View
if ( vm.CloseAction == null )
vm.CloseAction = new Action(() => this.Close());
}
Here is a simple example using the MVVM Light Messenger instead of an event. The view model sends a close message when a button is clicked:
public MainViewModel()
{
QuitCommand = new RelayCommand(ExecuteQuitCommand);
}
public RelayCommand QuitCommand { get; private set; }
private void ExecuteQuitCommand()
{
Messenger.Default.Send<CloseMessage>(new CloseMessage());
}
Then it is received in the code behind of the window.
public Main()
{
InitializeComponent();
Messenger.Default.Register<CloseMessage>(this, HandleCloseMessage);
}
private void HandleCloseMessage(CloseMessage closeMessage)
{
Close();
}
You can create new Event handler in the ViewModel like this.
public event EventHandler RequestClose;
protected void OnRequestClose()
{
if (RequestClose != null)
RequestClose(this, EventArgs.Empty);
}
Then Define RelayCommand for ExitCommand.
private RelayCommand _CloseCommand;
public ICommand CloseCommand
{
get
{
if(this._CloseCommand==null)
this._CloseCommand=new RelayCommand(CloseClick);
return this._CloseCommand;
}
}
private void CloseClick(object obj)
{
OnRequestClose();
}
Then In XAML file set
<Button Command="{Binding CloseCommand}" />
Set the DataContext in the xaml.cs File and Subscribe to the event we created.
public partial class MainWindow : Window
{
private ViewModel mainViewModel = null;
public MainWindow()
{
InitializeComponent();
mainViewModel = new ViewModel();
this.DataContext = mainViewModel;
mainViewModel.RequestClose += delegate(object sender, EventArgs args) { this.Close(); };
}
}
My proffered way is Declare event in ViewModel and use blend InvokeMethodAction as below.
Sample ViewModel
public class MainWindowViewModel : BindableBase, ICloseable
{
public DelegateCommand SomeCommand { get; private set; }
#region ICloseable Implementation
public event EventHandler CloseRequested;
public void RaiseCloseNotification()
{
var handler = CloseRequested;
if (handler != null)
{
handler.Invoke(this, EventArgs.Empty);
}
}
#endregion
public MainWindowViewModel()
{
SomeCommand = new DelegateCommand(() =>
{
//when you decide to close window
RaiseCloseNotification();
});
}
}
I Closeable interface is as below but don't require to perform this action. ICloseable will help in creating generic view service, so if you construct view and ViewModel by dependency injection then what you can do is
internal interface ICloseable
{
event EventHandler CloseRequested;
}
Use of ICloseable
var viewModel = new MainWindowViewModel();
// As service is generic and don't know whether it can request close event
var window = new Window() { Content = new MainView() };
var closeable = viewModel as ICloseable;
if (closeable != null)
{
closeable.CloseRequested += (s, e) => window.Close();
}
And Below is Xaml, You can use this xaml even if you don't implement interface, it will only need your view model to raise CloseRquested.
<Window 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:WPFRx"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:ViewModels="clr-namespace:WPFRx.ViewModels" x:Name="window" x:Class="WPFRx.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
d:DataContext="{d:DesignInstance {x:Type ViewModels:MainWindowViewModel}}">
<i:Interaction.Triggers>
<i:EventTrigger SourceObject="{Binding Mode=OneWay}" EventName="CloseRequested" >
<ei:CallMethodAction TargetObject="{Binding ElementName=window}" MethodName="Close"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<Button Content="Some Content" Command="{Binding SomeCommand}" Width="100" Height="25"/>
</Grid>
You can use Messenger from MVVMLight toolkit. in your ViewModel send a message like this:
Messenger.Default.Send(new NotificationMessage("Close"));
then in your windows code behind, after InitializeComponent, register for that message like this:
Messenger.Default.Register<NotificationMessage>(this, m=>{
if(m.Notification == "Close")
{
this.Close();
}
});
you can find more about MVVMLight toolkit here:
MVVMLight toolkit on Codeplex
Notice that there is not a "no code-behind at all rule" in MVVM and you can do registering for messages in a view code-behind.
You may treat window as a service (eg. UI service) and pass itself to viewmodel via an interface, as such:
public interface IMainWindowAccess
{
void Close(bool result);
}
public class MainWindow : IMainWindowAccess
{
// (...)
public void Close(bool result)
{
DialogResult = result;
Close();
}
}
public class MainWindowViewModel
{
private IMainWindowAccess access;
public MainWindowViewModel(IMainWindowAccess access)
{
this.access = access;
}
public void DoClose()
{
access.Close(true);
}
}
This solution have most upsides of passing the view itself to viewmodel without having downside of breaking MVVM, because though physically view is passed to viewmodel, the latter still don't know about the former, it sees only some IMainWindowAccess. So for instance if we wanted to migrate this solution to other platform, it would be only a matter of implementing IMainWindowAccess properly for, say, an Activity.
I'm posting the solution here to propose a different approach than events (though it's actually very similar), because it seems a little bit simpler than events to implement (attaching/detaching etc.), but still aligns nicely with MVVM pattern.
It's simple.
You can create your own ViewModel class for Login - LoginViewModel.
You can create view var dialog = new UserView(); inside your LoginViewModel.
And you can set-up Command LoginCommand into button.
<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding LoginCommand}" />
and
<Button Name="btnCancel" IsDefault="True" Content="Login" Command="{Binding CancelCommand}" />
ViewModel class:
public class LoginViewModel
{
Window dialog;
public bool ShowLogin()
{
dialog = new UserView();
dialog.DataContext = this; // set up ViewModel into View
if (dialog.ShowDialog() == true)
{
return true;
}
return false;
}
ICommand _loginCommand
public ICommand LoginCommand
{
get
{
if (_loginCommand == null)
_loginCommand = new RelayCommand(param => this.Login());
return _loginCommand;
}
}
public void CloseLoginView()
{
if (dialog != null)
dialog.Close();
}
public void Login()
{
if(CheckLogin()==true)
{
CloseLoginView();
}
else
{
// write error message
}
}
public bool CheckLogin()
{
// ... check login code
return true;
}
}
This is a way I did it pretty simply:
YourWindow.xaml.cs
//In your constructor
public YourWindow()
{
InitializeComponent();
DataContext = new YourWindowViewModel(this);
}
YourWindowViewModel.cs
private YourWindow window;//so we can kill the window
//In your constructor
public YourWindowViewModel(YourWindow window)
{
this.window = window;
}
//to close the window
public void CloseWindow()
{
window.Close();
}
I don't see anything wrong with the answer you chose, I just thought this might be a more simple way to do it!
In MVVM WPF I usually design my View as a UserControl. And It is just a matter of how you want to display It. If you want It to be in a Window, then you could do a WindowService class:
public class WindowService
{
//...
public void Show_window(object viewModel, int height, int width, string title)
{
var window = new Window
{
Content = viewModel,
Title = title,
Height = height,
Width = width,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Owner = Application.Current.MainWindow,
Style = (Style)Application.Current.FindResource("Window_style") //even style can be added
};
//If you own custom window style, then you can bind close/minimize/maxmize/restore buttons like this
window.CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, OnCloseWindow));
window.CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand, OnMaximizeWindow, OnCanResizeWindow));
window.CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand, OnMinimizeWindow, OnCanMinimizeWindow));
window.CommandBindings.Add(new CommandBinding(SystemCommands.RestoreWindowCommand, OnRestoreWindow, OnCanResizeWindow));
window.ShowDialog();
}
public void Close_window(object viewmodel)
{
//Close window
foreach (Window item in Application.Current.Windows)
{
if (item.Content == viewmodel) item.Close();
}
}
}
Using my approach is simple. Usually you want to close Window when something happens in It. So, when It does, just call Close_window method from corresponding ViewModel - the one which is DataContext of UserControl which is displayed in a Window. Look bottom example:
1.) We open Window from some Viewmodel:
public class MyViewModel // ViewModel where you open window
{
private readonly WindowService _windowservice // or inject/inherit from Base
public MyViewModel()
{
_windowservice = new WindowService();
}
private void Example_method()
{
//...Open window
_windowservice.Show_window(new WindowViewModel(),100,100,"Test window");
}
}
2.) Our Window is allready opened, now we want to close It :
public class WindowViewModel // ViewModel which is your Window content!
{
private readonly WindowService _windowservice // or inject/inherit from Base
public MyViewModel()
{
_windowservice = new WindowService();
}
private void Example_method()
{
//Close window
_windowservice.Close(this); //Pass a reference of viewmodel to method
}
}
This solution is far less elegant that other accepted answers, but for me It works. I use It widely in projects, so far no problems with It. But I'm sure that someone will come and say "That is a violation of MVVM principle".
You can close the current window just by using the following code:
Application.Current.Windows[0].Close();
System.Environment.Exit(0); in view model would work.

Separate View not updating from background worker Mvvm

I am having issues updating a separately opened window's progress bar from a background worker inside another class.
The program execution goes like this:
MainWindow loads
Click button to do some work and open a popup
progress bar (newly opened window)
Background worker does work
and reports progress to popup progress bar
Popup progress bar
hopefully updates.
The progress bar Value is bound to a property, which in the step-through debugger, looks to be getting updated okay by the background worker. These changes just are not reflected on the popup progress bar view. However, the binding is not broken because if I manually try and set the property value for the progress bar it works fine.
Furthermore, when I put the progress bar inside the initially started MainWindow view it updates fine. Any suggestions??
Here is the some code:
MainWindowViewModel
public class MainWindowViewModel: BaseViewModel
{
private void PerformSomeAction()
{
var popUpProgressBar = new PopUpProgressBarViewModel();
popUpProgressBar.Show(popUpProgressBar);
var worker = new BackgroundWorker { WorkerReportsProgress = true };
worker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args)
{
if (args.ProgressPercentage != popUpProgressBar.Progresser)
{
Progresser = args.ProgressPercentage;
popUpProgressBar.Progresser = args.ProgressPercentage;
}
};
worker.DoWork += delegate
{
for (int i = 0; i < 101; i++)
{
worker.ReportProgress(i);
System.Threading.Thread.Sleep(10);
}
MessageBox.Show("Done");
};
worker.RunWorkerAsync();
}
private int _progresser;
public int Progresser
{
get { return _progresser; }
set
{
if (_progresser == value) return;
_progresser = value;
OnPropertyChanged("Progresser");
}
}
private RelayCommand _startProcessing; //set private member
public ICommand StartProcessing //public field used by xaml binding
{
get
{
return _startProcessing = MakeCommandSafely(_startProcessing, () => PerformSomeAction());
}
}
}
PopUpProgressBarViewModel
public class PopUpProgressBarViewModel : BaseViewModel
{
private PopUpProgressBar _popUpProgressBar;
public void Show(PopUpProgressBarViewModel context)
{
_popUpProgressBar = new PopUpProgressBar {DataContext = context};
_popUpProgressBar.Show();
}
private int _progresser;
public int Progresser
{
get { return _progresser; }
set
{
if (_progresser == value) return;
_progresser = value;
OnPropertyChanged("Progresser");
}
}
}
For full solution file (so you can see whats happening), see here
As #Doug said, since you are already setting the DataContext:
_popUpProgressBar = new PopUpProgressBar {DataContext = context};
You can change the PopUpProgressBar to
<Window x:Class="OpeningWindow_With_ProgressBar.View.PopUpProgressBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:OpeningWindow_With_ProgressBar.ViewModel" Title="PopUpProgressBar" Height="150" Width="300">
<Grid>
<StackPanel>
<Label FontWeight="Bold">Loading Something</Label>
<ProgressBar Minimum="0" Maximum="100" Margin="0,10,0,0" Height="25px" Width="250px" Value="{Binding Path=Progresser, Mode=OneWay}"></ProgressBar>
<TextBlock Margin="10,10,0,0" Text="Details of loading..."></TextBlock>
</StackPanel>
</Grid>
You are creating two PopUpProgressBarViewModels. You've got one that's being created as a resource inside PopUpProgressBar.xaml, and the other one is being created in MainWindowViewModel (line 18).
Your XAML is bound to the one created inside PopUpProgressBar.xaml, while the one that you're updating is the one created in MainWindowViewModel.
If you can pare it down so only one is created, that should solve your problem.

Categories