Property reverts to original value once second window closes - c#

I have a ViewModel which is bound to my MainWindow. I have a property in my ViewModel I want to bind to a second window which opens after selecting a menu item. Here is the property I have bound to the second window. So far so good
private string _displayPathToLib;
public string DisplayPathToLib
{
get { return _displayPathToLib; }
set
{
_displayPathToLib = value;
OnPropertyChanged("DisplayPathToLib");
}
}
I use a command using the ICommand interface to open the second window. Here is a snippet
public void Execute(object parameter)
{
BrowseDialog winBrowseDialog = new BrowseDialog();
Nullable<bool> BrowseDialogResult = winBrowseDialog.ShowDialog();
The second window opens as it should and allows me to edit the text box that is displayed. I am able to to see the "DisplayPathToLib" property change when I type something in the textbox (by setting the debug break). But upon closing the window the value of "DisplayPathToLib" reverts to NULL. Below is a snippet of the code behind I am using to handle the ok button click
private void okButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
Why does the property keep reverting back to NULL? How do I get the "DisplayPathToLib" to retain its value??? I have tried everything. I also tried maintaining a MVVM pattern but could not get the OK button to work without code-behind. :-(

I solved my problem by setting the datacontext of my new window directly to my ViewModel. To ensure your ViewModel's data keeps the bound values from multiple windows set the new instance of your second window (or multiples windows) to your ViewModel like so...
class UserSettingsCommand : ICommand
{
MainVM _data; //MainVm is my ViewModel class
public UserSettingsCommand(MainVM data)
{
_data = data;
}
.
.
.
public void Execute(object parameter)
{
BrowseDialog winBrowseDialog = new BrowseDialog(); //Instantiate a new custom dialog box
winBrowseDialog.DataContext = _data; //THIS IS WHERE I SET MY VIEWMODEL TO THE NEW WINDOWS DATACONTEXT
Nullable<bool> BrowseDialogResult = winBrowseDialog.ShowDialog();
.
.
.
I am new to C# and I am just learning the MVVM pattern so while this is probably common knowledge, maybe someone new can save some time. Using the MVVM pattern with one window did not require this step. DataContext is set for my MainWindow in the MainWindow.xaml.cs file so I assumed this could be done for the second windows secondwin.xaml.cs file. The only way I got it to work was by setting the DataContext as shown in the code above ....not in the .cs file.

Related

How to change C# wpf background image when opening a new window?

My MainWindow has 4 radio buttons. The user has to choose one, then press on a button that opens up a new window. Depending on the radio button selected, I want to change the background that appears in the new window. This is my code:
public partial class Practice : Window
{
public Practice()
{
InitializeComponent();
if (((MainWindow)Application.Current.MainWindow).BinomialRadio.IsChecked == true)
{
}
else if (((MainWindow)Application.Current.MainWindow).HypergeometricRadio.IsChecked == true)
{
}
else if (((MainWindow)Application.Current.MainWindow).PoissonRadio.IsChecked == true)
{
Background = new ImageBrush(new BitmapImage(new Uri(BaseUriHelper.GetBaseUri(this), "images/poisson_practice_screen.jpg")));
}
else
{
}
}
}
The new window already has a default background that I set in the properties of the XAML code. This code above runs and executes well, but the picture does not change. I found a quick fix, which is basically to remove the background (so that the new window always has a blank background), and then set it every time it opens. Is there any better way to do this?
Thank you to everyone for their help
The reason that the background of the Practice window does not update is because you set its background in the constructor of the window, which only runs when the window is created. In order for it to update, you have to add event handlers on each of the checkboxes for the Checked event and update the background in the handler.
However, the easiest and most recommended way to do this is using data binding. Data binding is a construct in WPF and other frameworks where you declaratively indicate which properties are linked together, so that you don't have to update them manually. No writing tedious event event handlers or keeping track of complicated changes.
Practice.xaml.cs:
public partial class Practice : Window
{
// INotifyPropertyChanged implementation is important!
// Without it, WPF has no way of knowing that you changed your property...
public class PracticeModel : INotifyPropertyChanged
{
private BitmapImage _background;
public BitmapImage Background
{
get => _background;
set { _background = value; PropertyChanged?.Invoke(nameof(Background)); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
public Practice()
{
InitializeComponent();
// DataContext specifies which object the bindings are bound to
this.DataContext = new PracticeModel();
}
}
Practice.xaml:
<Window x:Class="MyApp.Practice" Background="{Binding Background}">
<!-- your content here; all other attributes of Window omitted for brevity -->
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public Practice.PracticeModel PracticeModel { get; set; } = new Practice.PracticeModel();
// ...
public OnButtonClicked(RoutedEventArgs e)
{
var window = new Practice();
// DataContext specifies which object the bindings are bound to
window.DataContext = this.PracticeModel;
window.Show();
}
public OnPoissonRadioChecked(RoutedEventArgs e)
{
PracticeModel.Background = new BitmapImage(new Uri(BaseUriHelper.GetBaseUri(this), "images/poisson_practice_screen.jpg"));
}
// likewise for other radio buttons ...
}
MainWindow.xaml:
<Window x:Class="MyApp.MainWindow">
<RadioButton Group="TheButtons" x:Name="BinomialRadio" IsChecked="True" Checked="OnBinomialRadioChecked" />
<RadioButton Group="TheButtons" x:Name="HypergeometricRadio" Checked="OnHypergeometricRadioChecked" />
<RadioButton Group="TheButtons" x:Name="PoissonRadio" Checked="OnPoissonRadioChecked" />
<RadioButton Group="TheButtons" x:Name="TheOtherRadio" Checked="OnTheOtherRadioChecked" />
</Window>
When you change the property on PracticeModel, the PropertyChanged event is fired. This tells WPF that the property has changed, and it automatically updates all of the relevant bindings. This will quickly become very useful when you want to have more than one dynamically updating property. Additionally, data binding can automatically convert from a string or a Uri to an ImageSource, so you might not even need to create the BitmapImage yourself (and if you don't have to, then don't.)
As you might have noticed, there are still event handlers in this code. That's because I didn't want to introduce too much complexity at the same time, and data-binding radio buttons properly can be kind of confusing for someone who is not accustomed to this. I hope this helps!

Why dynamically changed data not updated on MVVM binding here

I am using Mvvm approach in silverlight. where i try to bind a TextBox like this :
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text="{Binding ViewModelText}" Grid.Row="0"></TextBlock>
</Grid>
where this xaml.cs class behind this xaml is:
public partial class UIeLementRender : UserControl
{
public UIeLementRender()
{
InitializeComponent();
this.DataContext = new ViewModel.TabControlStuffViewModel.uiElementRendererViewModel();
}
}
and viewmodel class is:
public class uiElementRendererViewModel: GenericViewModel
{
private String viewModelText;
public String ViewModelText
{
get
{
return viewModelText;
}
set
{
viewModelText = value;
OnPropertyChanged("ViewModelText");
}
}
public uiElementRendererViewModel()
{
this.viewModelText = "Hii from UIelemnt rendering"; //this updated but below one is never updated.
}
public uiElementRendererViewModel(ProgramVersion pv) //This constructor is called dynamically on abutton click from other view model class but it never updates the viewModelText
{
this.viewModelText = "Hii changed UIelemnt rendering";
this.OnPropertyChanged("ViewModelText");
}
}
Now when i run the code it shows me "Hii from UIelemnt rendering"; (which is correct) but when i press a button from another viewmodel class dynamically, i wan to update the new text on viewModelText to "Hii changed UIelemnt rendering" . Which is not updated even i have done "this.OnPropertyChanged("ViewModelText");" in my second costructor. (it is still has the text of default constructor/without parameter)
How to update this nex text which is obtained dynamically ?
You need to set the actual property, not re-initialize the whole ViewModel:
viewModel.ViewModelText = "Hii changed UIelemnt rendering";
The call to OnPropertyChanged in your constructor is redundant, as that will run before any actual binding happens.
If you -have- to re-initialize the ViewModel, and there's no possible way around this, then you also need to re-initialize the view, or manually set the DataContext of the existing view to the new instance of the ViewModel class.
I've created a few sample files that illustrate how it -could- be done: http://pastebin.com/W6Yh7N6E
I'll try to illustrate how WPF would do the databinding with a short list here:
Create View
Create ViewModel (Instantiate, run constructor, etc.) - This is where the event is currently fired in your own example
Set ViewModel as DataContext (Which in your example never happens anyways)
WPF Updates its bindings with current values (So no need for another PropertyChanged event here)
There's a bunch more stuff happening behind the scenes, but these steps should be sufficient to give you a decent understanding of why the PropertyChanged event wouldn't do anything in a constructor.
When you create a new viewmodel from you button click, you actually have two instances of the viewmodel. The UI however is still bound to the first one, so whatever changes you make in the second, will never appear on the UI. You could set the second instance as new DataContext:
private void OnButtonClick(object sender, EventArgs e)
{
this.DataContext = new ViewModel.TabControlStuffViewModel.uiElementRendererViewModel(/*pass variable here*/);
}
But this is hardly what you actually want. What you should do instead is modify the existing viewmodel object:
private void OnButtonClick(object sender, EventArgs e)
{
var vm = this.DataContext as uiElementRendererViewModel; // Get the current view model and cast it to correct type
vm.ViewModelText = "Enter new text here"; // Update the text
}

Correct approach to UserControl creation when using MVVM

This is more of a conceptual question rather than a practical one. I'm just starting to learn the MVVM concept for developing UI , and I've come across a dillema I'm not sure the answer to:
Say I have a main window and a little pop-up window (meaning it's a small window with some UI elements in it). The structure of the program will look something like this:
MainWindow
model <-- MainWindowViewModel.cs <-- MainWindowView.xaml (containing no code-behind)
PopUpWindow (A UserControl)
model <-- PopUpWindowViewModel.cs <-- PopUpWindowView.xaml (containing no code-behind)
*the model is just a bunch of BL classes that are irrelevant for this question.
Now , lets say I want to create a new PopUp window from inside the MainWindowViewModel (or even save an instance of it in a private data-member). What is the correct way of doing so?
The way I see it I can't do something like this :
PopUpWindow pop = new PopUpWindow()
Because it kind of defeats the purpose of abstracting the view from the view model(What if a year from now i'll want to create a better version of the PopUpWindow using the same PopUpWindowViewModel?).
On the other hand , I can't initialize a new instnace of the PopUpWindow using just it's view model (The viewModel as I understand is not supposed to know anything about the view that will use it).
Hope it all makes sense... so what would you do in that situation?
*Just to clarify it further , let's say for argument's sake that the situation I'm describing is a button on the MainWindowView that upon clicking will open a PopUpWindowView.
Thanks in advnace.
I had somewhat a similar dilemma and I'll explain how I solved it.
Let's say you have MainWindow and a SettingsWindow, which you want to display when the SettingsButton is clicked.
You have two respective view models, MainWindowViewModel and SettingsViewModel, which you will be passing as their Window.DataContext properties.
Your MainWindowViewModel should expose an ICommand property named SettingsButtonCommand (or similar). Bind this command to the SettingsButton.Command.
Now your command should invoke something like this:
void OnSettingsButtonClicked()
{
var viewModel = new SettingsViewModel();
var window = new SettingsWindow();
window.DataContext = viewModel;
window.Show();
}
There is a slight issue when you want to use Window.ShowDialog(), because you need to resume execution.
For these cases I have an asynchronous variant of the DelegateCommand:
public sealed class AsyncDelegateCommand : ICommand
{
readonly Func<object, Task> onExecute;
readonly Predicate<object> onCanExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public AsyncDelegateCommand(Func<object, Task> onExecute)
: this(onExecute, null) { }
public AsyncDelegateCommand(Func<object, Task> onExecute, Predicate<object> onCanExecute)
{
if (onExecute == null)
throw new ArgumentNullException("onExecute");
this.onExecute = onExecute;
this.onCanExecute = onCanExecute;
}
#region ICommand Methods
public async void Execute(object parameter)
{
await onExecute(parameter);
}
public bool CanExecute(object parameter)
{
return onCanExecute != null ? onCanExecute(parameter) : true;
}
#endregion
}
You've specifically said that the popup is a UserControl so you can use basic data templating. First create view models for your main window and popup control:
public class MainViewModel : ViewModelBase
{
private PopUpViewModel _PopUp;
public PopUpViewModel PopUp
{
get { return _PopUp; }
set { _PopUp = value; RaisePropertyChanged(() => this.PopUp); }
}
}
public class PopUpViewModel : ViewModelBase
{
private string _Message;
public string Message
{
get { return _Message; }
set { _Message = value; RaisePropertyChanged(() => this.Message); }
}
}
The MainViewModel's PopUp member is initially null, we'll set it to an instance of PopUpViewModel when we want the popup to appear. To do that we create a content control on the main window and set it's content to that member. We also use a data template to specify the type of child control to create when the popup view model has been set:
<Window.Resources>
<DataTemplate DataType="{x:Type local:PopUpViewModel}">
<local:PopUpWindow />
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Content="Show PopUp" Click="Button_Click_1" HorizontalAlignment="Left"/>
<ContentControl Content="{Binding PopUp}" />
</StackPanel>
I'm doing a big no-no here by creating the view model in the code-behind along with a click handler, but it's just for illustrative purposes:
public partial class MainWindow : Window
{
MainViewModel VM = new MainViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = this.VM;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.VM.PopUp = new PopUpViewModel { Message = "Hello World!" };
}
}
That's it! Click the button, popup window appears underneath it showing the content. Now it may not always be this simple, sometimes you may want to create multiple children on a parent control...in that case you'd set up an ItemsControl, set its panel to a Grid (say) and modify the data templates to set the Margin etc on each element to position them. Or you may not always know what type of view model is going to be created, in which case you need to add multiple data templates for each type you're expecting. Either way you still have good separation of concerns because it is the views that are deciding how to display the content in the view models. The view models themselves still don't know anything about the views and they can be unit-tested etc independently.

Triggering RelayCommand from code-behind

I have a Main Page for my application that is formed froma number of other windows. One of which is the settings for my applications and is open/closed by clicking a button from my Main Page. This window has a View Model as well as two buttons, Save and Cancel. Pressing Cancel is used to restore the previous settings and Save just stores them. Now, when I use the main menu to close the Properties I want the Cancel to be called and am wondering about the best way to do this.
So in my view model I have something like this:
public RelayCommand CancelRC { get; private set; }
public PropertiesViewModel
{
CancelRC = new RelayCommand(RestoreProperties)
}
private RestoreProperties
{
// Restore
}
In my MainPage.xaml.cs
private void Properties_Click(object sender, RoutedEventArgs e)
{
if (PropertiesForm.Visibility == Visibility.Collapsed)
{
PropertiesForm.Visibility = Visibility.Visible;
PropertiesForm.IsSelected = true;
}
else
{
PropertiesForm.Visibility = Visibility.Collapsed;
IncidentForm.IsSelected = true;
// Send a Cancel relaycommand to the Properties View Model?
}
}
The obvious solution to me is to trigger the RelayCommand manually but I am not sure this is the most appropriate way to do it and I am not sure how you would trigger that anyway. So it this the way to do it or is there a more preferred way to do something like this?
Similarly is manually hiding and showing the Properties via the Main Page like this the best way to do it?
Remove the Properties_Click method and in your xaml do the following:
<Button Command="{Binding CancelRC}">Properties</Button>
This will cause the button to use RelayCommand.CanExecute and RelayCommand.Execute with no code on your part. The code assumes the window datacontext is set to your View Model.

Property in ViewModel not displayed in View

I have a ViewModel called MainWindowViewModel. In this I have a property that shows a modal window when an error occurs. This works fine as long as an error occurs during start-up.
When an error occurs after start-up, in SubViewModel, I invoke the parametrized constructor in MainWindowViewModel.
MainWindowViewModel.cs
public MainWindowViewModel()
{
if (!isServerRunning)
{
this.ModalWindow = new LogViewModel("Server is down.");
}
else
{
this.ModalWindow = new LogViewModel();
}
}
public MainWindowViewModel(string logMessage)
{
this.ModalWindow = new LogViewModel(logMessage);
}
public LogViewModel ModalWindow
{
get
{
return _modalWindow;
}
set
{
_modalWindow = value;
OnPropertyChanged("ModalWindow");
}
}
MainWindow.xaml
....
<Grid>
<vw:LogView Content="{Binding Path=ModalWindow}"/>
</Grid>
MainWindowViewModel is bound to MainWindow.xaml
SubViewModel is bound to SubView.xaml
MainWindow.xaml loads multiple views, one of them is SubView.
In App.xaml I have created an instance of the ViewModel and bound it to MainWindow.
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow mainWindow = new MainWindow();
MainWindowViewModel viewModel = new MainWindowViewModel();
mainWindow.DataContext = viewModel;
mainWindow.Show();
}
What I realized was that the modal window shows up when an error occurs after start-up if I create the modal window property in SubViewModel and bind it to SubView. But this is not ok since SubView is only a DockPanel covering 1/4 of the MainWindow. I.e. 1/4 is only covered with a modal window instead of the whole MainWindow.
I am not sure why modal window does not appear in MainWindow when I call the parametrized constructor from SubViewModel. When I debug I see that the part _modalWindow = value; has correct values but in any case the modal window does not show up.
I am binding the ViewModel to the Datacontext of the MainWindow. That's is probably why I see the Modal window when error occurs on start-up. For errors after start-up: Must I (from SubViewModel where I invoke the parametrized constructor in MainWindowViewModel) do some kind of binding to the datacontext of the Mainwindow again? How is the best way of doing this without having to create a new instance of MainWindow? Because MainWindow should only be created once.
Any help is much appreciated.
It looks like you are recreateing the ViewModel every time an error occours. If so you would need to reset the binding in the view as well, which would defeat the purpose of MVVM.
Rather have one instance of the ViewModel and propagate the errors to the View using OnPropertyChanged().
There are of course many ways of doing this, but I usually keep a reference to the ViewModel in my View, and then a reference to the Model in the ViewModel. This way the Model is completely decoupled from the View/ViewModel and likewise the ViewModel is decoupled from the View.
You should not create MainWindowViewModel again and again. It should be created once and set to the datacontext of the mainwindow.
The problem is with the approach that you are taking to show dialog boxes. This is making things complicated.
The best solution to show dialog boxes is to use mvvmlight toolkit's messenger. Check this for some hints on it's usage.
This is how you could use mvvmlight toolkit's messenger to show dialogboxes:
View:
Messenger.Default.Register<DialogMessage>(
view,
msg =>
{
var result = MessageBox.Show(
msg.Content,
msg.Caption,
msg.Button,
msg.Icon);
}
);
ViewModel:
private void ShowMessageBox(string msgStr, string capStr, MessageBoxButton btn, MessageBoxImage img)
{
var message = new DialogMessage(msgStr, null)
{
Button = btn,
Caption = capStr,
Icon = img
};
Messenger.Default.Send(message);
}
Just call the above method (ShowMessageBox) from any viewmodel to show a dialogbox.

Categories

Resources