Property in ViewModel not displayed in View - c#

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.

Related

View does not update with changing viewModel

I have a userControl named SensorControl which I want to bind its dataContext to a viewModel class named SensorModel.
The binding is successful and the initial values of viewModel is visible in the userControl, but the problem is that when I change the viewModel's properties, the userControl does not update, and I have to manually update that (by assigning null to its dataContext and assigning the viewModel again).
Here is a simplified example of my code.
SensorControl.xml:
<UserControl ...[other attributes]... DataContext={Binding Model}>
.
.
.
<Label Content="{Binding Title}"/>
.
.
.
</UserControl>
SensorControl.xml.cs (code-behind):
public partial class SensorControl : UserControl
{
public SensorModel model;
public SensorModel Model
{
get { return model; }
set { model = value; }
}
public SensorControl(SensorModel sm)
{
Model = sm;
}
}
MainWindow.xml.cs:
public partial class MainWindow : Window
{
public SensorModel sensorModel_1;
public SensorControl sensorControl_1;
public MainWindow()
{
InitializeComponent();
sensorModel_1 = new SensorModel(...[some arguments]...);
sensorControl_1 = new SensorControl(sensorModel_1);
mainGrid.Children.Add(sensorControl_1);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
sensorModel_1.Title = "myTitle";
//The UserControl does not update
}
0) I implemented INotifyPropertyChanged in SensorModel
1) The reason I need this, is that there is only one single concept of 'Sensor' in my project (it is a real electronic sensor) and therefore I have a single model for it (that deal with the real sensor, the database, etc), but in the UI I have multiple userControls for presenting different aspects of Sensor. So I have to create one instance of model (SensorModel) for each real sensor, and multiple userControls must bind to that (each one uses different parts of model).
2) I'm not that new to WPF, but I'm kind of new to MVVM and it's possible that I misunderstand something essential, so I would appreciate if someone could clearly explain the correct approach.
Thanks in advance
In your UserControl, remove the DataContext attribute and add an x:Name attribute. Then in your Label, bind like this:
<UserControl x:Name="uc">
<Label Content="{Binding ElementName=uc,Path=Model.Title}" />
</UserControl>
I believe the issue is the DataContext can't be set to Model because binding works off the parent's DataContext which will be based on mainGrid when it gets added as a child to that. Since the property "Model" doesn't exist in maiGrid's DataContext no binding will occur so your update won't reflect. Getting the DataContext of a UserControl properly can be tricky. I use the ElementName quite a bit or create DependencyProperties on the UserControl and then set them from the parent who will be using the control.
You need to set the DataContext to your ViewModel class in your View, and if you're applying the MVVM pattern, you should use ICommand for actions. Maybe it would be better If you'd implement a MainView class that does the logic in the background instead in the MainWindow class.
public MainWindow()
{
InitializeComponent();
DataContext = new MainView();
// sensorModel_1 = new SensorModel(...[some arguments]...);
// sensorControl_1 = new SensorControl(sensorModel_1);
// mainGrid.Children.Add(sensorControl_1);
}
Your MainView class :
public class MainView {
public SensorControl Control {get; internal set;}
...
}
And in your xaml change the binding :
<Label Content="{Binding Control.Model.Title}"/>
Thanks to all of you guys, I took your advices and finally I found a way.
1) I implemented an event in the SensorModel that fires every time any of properties changes (name ModelChanged)
2) Then as Merve & manOvision both suggested, I declared a dependency property in the SensorControl (of type SensorModel) and bind ui elements to that (Binding Path=Model.Title)
3) Then I used the ModelChanged event of this dependency property in the SensorControl and raise an event (of type INotifyPropertyChanged) so the bindings update their value
It works fine.

How to load ViewModel/View in WPF under CaliburnMicro

I have a timer in my wpf application wich every 5 minutes ask a WCF service. If the service have message for my application, I get a list which contains text data and a specific code.
This code give an information about the view which must be loaded to print the data.
I have two ViewModel (the data source is the same for both): One for a Ticker > one view and One for Popup > two view
Project files :
View
Popup
PopHighView.xaml
PopMediumView.xaml
Ticker
TickerLayout.xaml
TickerNormal.xaml
ViewModel
PopViewModel
TickerViewModel
Models
AlertModel.cs
ViewParsers
AlertParser.cs
Datasource :
public class AlertParser : IAlertParser{
AlertServiceClient service;
public List<AlertModel> TickerAlertData()
{
try
{
service = new AlertServiceClient();
List<AlertModel> items = (from item in service.GetActiveAlert() select new AlertModel
{
Alertid= item.AlertId,
Alertstartdate = item.AlertStartDate,
Alerttitle = item.AlertTitle,
Alerttxt = item.AlertText
}).ToList();
return items;
}
catch
{
return null;
}
}
}
When my application is launched, there is no loaded view, only a icon in the systray(with wpf notifyicon).
My problem is, under theses circonstances, I don't understand how I could loaded a couple ViewModel/View, and pass the data to them, when my timer return a true from my service.
Many examples on the web have a mainview loaded, that's why I'm lost (like Conductor example on caliburn micro page).
Thanks for any help !
Edit :
Ok, My timer look like that :
if (service.IsAlertReady()=true)
{
string hostName = Dns.GetHostName();
string myIP = Dns.GetHostEntry(hostName).AddressList[0].ToString();
service.IsAlertForMe(myIP);
if(service.IsAlertForMe(myIP) == true)
{
ShellViewModel shell = new ShellViewModel();
shell.ShowMediumPop();
}
else
...
ShellViewModel
public class ShellViewModel : Conductor<object>
{
public void ShowMediumPop()
{
ActivateItem(new PopViewModel());
}
}
PopViewModel
public class PopViewModel : screen
{
protected override void OnActivate()
{
base.OnActivate();
}
}
PopView.Medium
<UserControl x:Class="TerminalClientProto.View.PopView"
...
cal:View.Model="{binding}"
cal:View.Context="Medium"
>
I'm sorry but I don't understand how I could launch my view when my Ticker tick. I've read the documentation, but I need some hints to understand this mechanism.
A program, any program, including the very program that contains the views you want to display can show a view in a number of ways. Here's a few:
var app = new App();
app.InitializeComponent();
app.Run();
Or you can start the view directly:
var view = new MyView();
view.Show();
// or
view.ShowDialog();
If the view is a MainWindow, then you can create a ContentControl area within the view to inject the Usercontrol containing the sub-view of what you want displayed. This still requires the MainWindow to be open... So the examples above would also work when injecting UserControls into a MainWindow. The act of injecting a User control is setting the ContentControl's Content to an instance of the User Control itself. Eventhandlers can handle this scenario nicely...
public void NewUserControlInMainWindow(object sender, UserControl uc){
//XCC = the Xaml content control in main window
XCC.Content = uc;
}
I'm not really sure how Caliburn does view injection....

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.

Fire command from custom canvas

I develop application with MVVM pattern. In my viewmodel project I have custom Command. In View I have custom canvas. I want to fire command from my canvas code behind but I dont have idea how.
My app startup (startup is in other project):
private void ApplicationStartup(object sender, StartupEventArgs e)
{
// init main ViewModel
var viewModel = new MainViewModel(Container);
// init and show window
var mainWindow = new MainWindow();
mainWindow.Show();
mainWindow.DataContext = viewModel;
}
I dont want to have reference to ViewModel in View, because I need reference to View in ViewModel.
Thanks!
The way you usually do this is implement your own COMMAND in the CustomCanvas class. After that, you bind that command against command handler in viewmodel.
Just like you do with button:
<Button
Command="{Binding YourVmCommand}"
/>
Makes sense?
To implement such functionality, you need to hack through ButtonBase.cs and see how it's done.
There's a lot code involved, but the idea is very simple.
1) Create new property in your View.
public ICommand Command { get; set; }
2) Bind to it in XAML
3) Execute it in View, when you need.
if(Command != null && Command.CanExecute(parameter))
Command.Execute(parameter);

Property reverts to original value once second window closes

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.

Categories

Resources