How to switch multiple views in WPF MVVM Catel application? - c#

In MS VS 2015 Professional I develop C# WPF MVVM application using Catel as MVVM framework. My problem is I don't know how to realize switching among multiple views in one window using buttons. Below I briefly describe my application. The MainWindow has three buttons
<catel:Window x:Class="FlowmeterConfigurator.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com"
ResizeMode="CanResize">
<catel:StackGrid x:Name="LayoutRoot">
<catel:StackGrid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
</catel:StackGrid.RowDefinitions>
<ToolBar>
<Button Name="btnConnectDisconnect" Content="Connect/Disconnect"/>
<Button Name="btnFieldSettings" Content="Field Settings"/>
<Button Name="btnCalibration" Content="Flowmeter Calibration"/>
</ToolBar>
</catel:StackGrid>
</catel:Window>
Application MainWindow has a ViewModel. For brevity I don't show it here. In addition to MainWindow there are three views in my application: ConnectDisconnectView, CalibrationView and FieldSettingsView. For brevity I show here only one of them (FieldSettingsView) because all of others are created in the same manner on the base of catel:UserControl.
<catel:UserControl x:Class="FlowmeterConfigurator.Views.FieldSettingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com">
<catel:StackGrid>
<catel:StackGrid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</catel:StackGrid.RowDefinitions>
<catel:StackGrid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</catel:StackGrid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Flowmeter Serial Number"/>
<TextBox Name="SerialNumber" Grid.Row="0" Grid.Column="1"/>
</catel:StackGrid>
</catel:UserControl>
Each of these views has a Model. I show here only one of these Models because all of them created in the same manner.
using Catel.Data;
namespace FlowmeterConfigurator.Models
{
/// <summary>
/// Field Settings Model.
/// </summary>
public class FieldSettingsModel : SavableModelBase<FieldSettingsModel>
{
/// <summary>
/// Returns flowmeter serial number.
/// </summary>
public string SerialNumber
{
get { return GetValue<string>(SerialNumberProperty); }
set { SetValue(SerialNumberProperty, value); }
}
/// <summary>
/// Register SerialNumber property.
/// </summary>
public static readonly PropertyData SerialNumberProperty = RegisterProperty("SerialNumber", typeof(string), null);
}
}
Each of these views has a ViewModel. I show here only one of these ViewModels because all of them created in the same manner.
using Catel;
using Catel.Data;
using Catel.MVVM;
using FlowmeterConfigurator.Models;
namespace FlowmeterConfigurator.ViewModels
{
/// <summary>
/// Field settings ViewModel.
/// </summary>
public class FieldSettingsViewModel : ViewModelBase
{
/// <summary>
/// Creates a FieldSettingsViewModel instance.
/// </summary>
/// <param name="fieldSettingsModel">Field settings Model.</param>
public FieldSettingsViewModel(FieldSettingsModel fieldSettingsModel)
{
Argument.IsNotNull(() => fieldSettingsModel);
FieldSettings = fieldSettingsModel;
}
/// <summary>
/// Returns or sets Field Settings Model.
/// </summary>
[Model]
public FieldSettingsModel FieldSettings
{
get { return GetValue<FieldSettingsModel>(FieldSettingsProperty); }
set { SetValue(FieldSettingsProperty, value); }
}
/// <summary>
/// Here I register FieldSettings property.
/// </summary>
public static readonly PropertyData FieldSettingsProperty = RegisterProperty("FieldSettings", typeof(FieldSettingsModel), null);
/// <summary>
/// Returns or sets flowmeter serial number.
/// </summary>
[ViewModelToModel("FieldSettings")]
public string SerialNumber
{
get { return GetValue<string>(SerialNumberProperty); }
set { SetValue(SerialNumberProperty, value); }
}
/// <summary>
/// Here I register SerialNumber property.
/// </summary>
public static readonly PropertyData SerialNumberProperty = RegisterProperty("SerialNumber", typeof(string), null);
}
}
Directly after my application loading, ConnectDisconnectView must be displayed. And then user can switch the views at will using the buttons on MainWindow toolbar. The switching among the Views must be in the following manner: if (for example) the current displayed view is "ConnectDisconnectView" and user presses "Field Settings" button then "ConnectDisconnectView" view must disappear from MainWindow and "FieldSettingsView" view must appear and must be displayed in MainWindow. And so on. That is when pressed appropriate button in MainWindow toolbar (for example "Flowmeter Calibration") the appropriate view (CalibrationView) must be displayed in MainWindow and other views must not be displayed. How can I realize this capability in my application? Your help will be appreciate highly.
P.S. Of course as you see the number and content of Views are reduced here for brevity and clarity. In real world the number of Views in my application is about 20 - 25 and they must contain complex graphics and table information.

First I show you xaml code:
<catel:Window.Resources>
<catel:ViewModelToViewConverter x:Key="ViewModelToViewConverter" />
</catel:Window.Resources>
<catel:StackGrid x:Name="LayoutRoot">
<ContentControl Content="{Binding CurrentPage, Converter={StaticResource ViewModelToViewConverter}}" />
<ToolBar>
<Button Name="btnConnectDisconnect" Command={Binding Connect} Content="Connect/Disconnect"/>
<Button Name="btnFieldSettings" Command={Binding Field} Content="Field Settings"/>
<Button Name="btnCalibration" Command={Binding Calibration} Content="Flowmeter Calibration"/>
</ToolBar>
</catel:StackGrid>
Then in c# code you need this:
using Catel.Data;
using Catel.MVVM;
using System.Threading.Tasks;
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
this.Connect = new Command(HandleConnectCommand);
this.Field = new Command(HandleFieldCommand);
this.Calibration = new Command(HandleCalibrationCommand);
this.CurrentPage = new ConnectViewModel();
}
/// <summary>
/// Gets or sets the CurrentPage value.
/// </summary>
public IViewModel CurrentPage
{
get { return GetValue<IViewModel>(CurrentPageProperty); }
set { SetValue(CurrentPageProperty, value); }
}
/// <summary>
/// Register the CurrentPage property so it is known in the class.
/// </summary>
public static readonly PropertyData CurrentPageProperty = RegisterProperty("CurrentPage", typeof(IViewModel), null);
public Command Connect { get; private set; }
public Command Field { get; private set; }
public Command Calibration { get; private set; }
protected override async Task InitializeAsync()
{
await base.InitializeAsync();
// TODO: subscribe to events here
}
protected override async Task CloseAsync()
{
// TODO: unsubscribe from events here
await base.CloseAsync();
}
private void HandleCalibrationCommand()
{
this.CurrentPage = new CalibrationViewModel();
}
private void HandleFieldCommand()
{
this.CurrentPage = new FieldViewModel();
}
private void HandleConnectCommand()
{
this.CurrentPage = new ConnectViewModel();
}
}
When you start your application CurrentPage is to be loaded with data context ConnectViewModel(). And then with commands from buttons you can change date context for another view model.

One way to solve this problem is using regions from Prism. Catel provides an extension for Prism so you can activate view models in specific regions.

Related

How do I bind a listbox to the property of a property using MVVM?

I am trying to populate a listbox with a bunch of serial data. I am saving the data to an ObservableCollection named SerialData (SerialData is type SerialMessage, a class that contains a bunch of data including 2 strings, time stamp and Message that I want to display). The ObservableCollection is a member of the DataCollector class. In the MainWindowViewModel I have declared the DataCollector and made it public. In my MainWindow the datacontext is set to MainWindowViewModel.
I want to bind the SerialData to a listbox on the MainWindow where I display the timestamp and message. I have tried several methods of binding the path to the SerialData but it does not show up in the listbox. I have confirmed that SerialData is updated correctly.
Is it possible to bind to the property of a property and display it's members?
My Code snippets:
SerialMessage class
...
/// <summary>
/// The time the message was received
/// </summary>
public DateTime TimeRecived
{
get;
set;
}
/// <summary>
/// The message on the serial bus
/// </summary>
public String Message
{
get;
set;
}
...
Mainwindow
...
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
...
<ListView Grid.Column="0" Grid.Row="1" Name="MessageList" Margin="10" ItemsSource="{Binding Path=DC.SerialData}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding TimeRecived, StringFormat=dd-MM-yyyy HH:mm:ss.fff}"></TextBlock>
<TextBlock Text="{Binding Message}" Margin="10,0,0,0"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
...
MainWindowViewModel
...
/// <summary>
/// Instance of data collector
/// </summary>
public DataCollector DC
{
get
{
return m_dataCollector;
}
}
...
DataCollector Class
...
/// <summary>
/// All of the Data recived on the serial Bus as it comes in
/// </summary>
public ObservableCollection<SerialMessage> SerialData
{
get;
private set;
}
...
There could be several reasons for not seeing the collection data.
One possibility is a missing notification.
If you assign a value to the SerialData collection after the initial XAML loading routines have finished, your view won't notice that you've set SerialData later on.
You need to raise INotifyPropertyChanged.PropertyChanged in the setter to notify the view of the property change.
class DataCollector : INotifyPropertyChanged {
...
public event PropertyChangedEventHandler PropertyChanged;
...
private ObservableCollection<SerialMessage> _serialData;
....
//somewhere some line code assigns to SerialData
SerialData = GetSerialData();
/// <summary>
/// All of the Data recived on the serial Bus as it comes in
/// </summary>
public ObservableCollection<SerialMessage> SerialData
{
get => _serialData;
private set {
if (value != _serialData)
{
_serialData = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventsArgs(nameof (SerialData)));
}
}

Can an embedded winform chart be bound to an observable collection in WPF

I wanted to embed a winform chart control in a WPF window which shall be bound to an observablecollection filled by entering data in a WPF DataGrid.
The observablecollection is needed because i fill it by using a WPF-DataGrid in which i can insert or update data.
So i added to my WPF-project the following dependencies:
- System.Windows.Forms
- System.Windows.Forms.DataVisualization
For a first test i hardcoded in the constructor of the WPF window some data in the observablecollection and bound the chart control.
In that case the display in the chart works fine.
But in the final version i want to insert and/or update data in the DataGrid and the chart shall display that data all at once.
Is it possible to manage that?
Here is the code for the window and the classes as example.
The WPF window MainWindow.xaml:
<Window x:Class="StepFunctions.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
xmlns:winformchart="clr-namespace:System.Windows.Forms.DataVisualization.Charting;assembly=System.Windows.Forms.DataVisualization"
xmlns:local="clr-namespace:StepFunctions"
mc:Ignorable="d"
Title="StepFunctions"
Height="350"
Width="525">
<Grid x:Name="maingrid">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!--DataGrid for insert and update of step function data-->
<DataGrid x:Name="grd_stepdata"
Grid.Row="0"
Grid.Column="0"
Margin="5"
AutoGenerateColumns="False"
CanUserAddRows="True"
RowEditEnding="grd_stepdata_RowEditEnding"
ItemsSource="{Binding StepDataSource, NotifyOnSourceUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Columns>
<DataGridTextColumn x:Name="col_LowerComparer" Binding="{Binding LowerComparer, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Lower comparer"/>
<DataGridTextColumn x:Name="col_LowerBound" Binding="{Binding LowerBound, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Lower bound"/>
<DataGridTextColumn x:Name="col_StepValue" Binding="{Binding StepValue, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Value"/>
</DataGrid.Columns>
</DataGrid>
<!--Chart for displaying the step function data-->
<WindowsFormsHost x:Name="host"
Grid.Row="0"
Grid.Column="1"
Margin="5">
<winformchart:Chart x:Name="myWinformChart"
Dock="Fill">
<winformchart:Chart.Series>
<winformchart:Series Name="series" ChartType="Line"/>
</winformchart:Chart.Series>
<winformchart:Chart.ChartAreas>
<winformchart:ChartArea/>
</winformchart:Chart.ChartAreas>
</winformchart:Chart>
</WindowsFormsHost>
<!--Button for test-->
<Button x:Name="btn_do"
Grid.Row="2"
Grid.Column="0"
Margin="5"
Click="btn_do_Click">Do it</Button>
</Grid>
</Window>
The code-behind of MainWindow.xaml:
using StepFunctions.ViewModels;
using System.Windows;
using System.Windows.Controls;
namespace StepFunctions
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MainWindowViewModel vm = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = vm;
// These lines are just for the first test.
// Normally these lines would be out-commented.
AddStepdata();
ChartDataRefresh();
}
// When the user leaves a DataGrid-row after insert or update the chart shall be refreshed.
private void grd_stepdata_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
ChartDataRefresh();
}
private void AddStepdata()
{
vm.StepDataSource.Add(new StepData("<", 10, 1));
vm.StepDataSource.Add(new StepData("<", 100, 2));
vm.StepDataSource.Add(new StepData("<", 1000, 3));
}
private void ChartDataRefresh()
{
myWinformChart.DataSource = vm.StepDataSource;
myWinformChart.Series["series"].XValueMember = "LowerBound";
myWinformChart.Series["series"].YValueMembers = "StepValue";
}
/// <summary>
/// For testing the refresh of the chart after the window was loaded.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_do_Click(object sender, RoutedEventArgs e)
{
AddStepdata();
ChartDataRefresh();
}
}
}
The viewmodel:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace StepFunctions.ViewModels
{
public class MainWindowViewModel : INotifyPropertyChanged
{
/// <summary>
/// Eventhandler for signalising that a property has changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<StepData> stepdataSource = new ObservableCollection<StepData>();
public ObservableCollection<StepData> StepDataSource
{
get { return stepdataSource; }
set
{
stepdataSource = value;
RaisePropertyChanged("StepDataSource");
}
}
/// <summary>
/// Informs the target which is bound to a property, that it's source was changed and that it shall update.
/// </summary>
/// <param name="propertyName">The name of the property.</param>
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And finally the class StepData which is the base for the observablecollection:
namespace StepFunctions.ViewModels
{
/// <summary>
/// Class for data of stepfunctions.
/// </summary>
public class StepData
{
/// <summary>
/// The constructor.
/// </summary>
public StepData()
{
// Do nothing
}
public StepData(string lowerComparer, double lowerBound, double stepValue)
{
LowerComparer = lowerComparer;
LowerBound = lowerBound;
StepValue = stepValue;
}
public string LowerComparer { get; set; }
public double LowerBound { get; set; }
public double StepValue { get; set; }
}
}
I got it!
The chart has to be generated in code-behind, not in the XAML.
So the method ChartDataRefresh has to look like that:
private void ChartDataRefresh()
{
Chart myWinformChart = new Chart();
myWinformChart.Dock = System.Windows.Forms.DockStyle.Fill;
Series mySeries = new Series("series");
mySeries.ChartType = SeriesChartType.Line;
myWinformChart.Series.Add(mySeries);
ChartArea myArea = new ChartArea();
myWinformChart.ChartAreas.Add(myArea);
myWinformChart.DataSource = vm.StepDataSource;
myWinformChart.Series["series"].XValueMember = "LowerBound";
myWinformChart.Series["series"].YValueMembers = "StepValue";
host.Child = myWinformChart;
}
While entering data in the WPF-DataGrid the Winform chart control is refreshed and the data displayed as a line for checking if the given data is correct.

HubSection with compiled binding

I'm trying to grasp the new compiled bindings, but right at the start I get stopped by this simple problem.
I have Hub control with one HubSection. The content of this section is an ItemsControl that needs to bind to view models' observable collection. I can't get this binding to work as I expect it to.
<Pivot x:Name="rootPivot" Style="{StaticResource TabsStylePivotStyle}">
<PivotItem>
<Hub>
<HubSection Header="News">
<DataTemplate x:DataType="local:HomePage">
<ItemsControl ItemsSource="{x:Bind ViewModel.NewsItems, Mode=OneWay}" />
ViewModel property is just a property and is instantiated before InitializeComponents() call. NewsItems is observable collection inside view model that is filled after the page has loaded - asynchronously (web request).
What am I doing wrong here?
EDIT: Code-behind
HomePage.xaml.cs
/// <summary>
/// Home pag view.
/// </summary>
public sealed partial class HomePage : Page
{
/// <summary>
/// Initializes a new instance of the <see cref="HomePage"/> class.
/// </summary>
public HomePage()
{
// Retrieve view model
this.ViewModel = ViewModelResolver.Home;
// Trigger view model loaded on page loaded
this.Loaded += (sender, args) => this.ViewModel.LoadedAsync();
this.InitializeComponent();
}
/// <summary>
/// Gets the view model.
/// </summary>
/// <value>
/// The view model.
/// </value>
public IHomeViewModel ViewModel { get; }
}
HomePageViewModel.cs
/// <summary>
/// Home view model.
/// </summary>
public sealed class HomeViewModel : IHomeViewModel
{
/// <summary>
/// Occurs on page loaded.
/// </summary>
public async Task LoadedAsync()
{
// Retrieve news items
var news = await new NewsService().GetNewsAsync();
foreach (var newsItem in news)
this.NewsItems.Add(newsItem);
}
/// <summary>
/// Gets the news items.
/// </summary>
/// <value>
/// The news items.
/// </value>
public ObservableCollection<IFeedItem> NewsItems { get; } = new ObservableCollection<IFeedItem>();
}
This is indeed an interesting question. I guess the issue is that, unlike typical DataTemplate like the following (see its parent ListView is binding to some known data Model.Items)
<ListView ItemsSource="{x:Bind Model.Items}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Item">
<Grid>
<TextBlock Text="{x:Bind Name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
your top level DataTemplate however, doesn't know where the data comes from.
So the fix is to tell the HubSection to bind the right data - in this case, the HomePage.xaml.cs instance. So, try adding this to your Hub
<Hub DataContext="{x:Bind}">
Or simply add
this.InitializeComponent();
this.DataContext = this;
Either way should fix your issue.

MVVM Pattern Query

I am learning WPF with MVVM Design pattern and trying to understand how to get some things done outside of the code behind.
I have a login page, pictured below.
I have a password control I took from http://www.wpftutorial.net/PasswordBox.html.
I would for now for the case of simple understanding, like to ask you if my code is in the right class/set correctly to abide to MVVVM regulations with seperation of concerns.
I currently have an if statement to check if the details match a name string and a password string.
The code is in the code behind. I just wonder if this is correct with regards to MVVM. I wonder how you implement this in a ViewModel?
private void OK_Click(object sender, RoutedEventArgs e)
{
if (emp.Name == "ep" && emp.Password == "pass")
{
MessageBox.Show("namd and Pw accepted");
//open new page
var HomeScreen = new HomeScreen();
HomeScreen.Show();
}
else
{
//deny access
MessageBox.Show("Incorrect username and password");
}
}
Instead of implementing the button click handler in the code behind, use an ICommand and bind it to the button's event in XAML.
Here is a really great tutorial that got me starting in MVVM:
WPF Apps With The Model-View-ViewModel Design Pattern
[Edited to add sample code]
Heres a simple code example to do just what your example does, but in MVVM style and without any code-behind code at all.
1) Create a new WPF Solution, for this small example I named it simply "WpfApplication".
2) Edit the code of the automatically created MainWindow.xaml:
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:WpfApplication"
Title="MainWindow" Height="234" Width="282">
<!-- Create the ViewModel as the initial DataContext -->
<Window.DataContext>
<viewModel:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TextBox Height="23"
HorizontalAlignment="Left"
Margin="70,31,0,0"
Name="textBox1"
VerticalAlignment="Top"
Width="120"
Text="{Binding Path=Name}"/>
<TextBox Height="23"
HorizontalAlignment="Left"
Margin="70,72,0,0"
Name="textBox2"
VerticalAlignment="Top"
Width="120"
Text="{Binding Path=Password}" />
<Label Content="Name"
Height="28"
HorizontalAlignment="Left"
Margin="22,29,0,0"
Name="label1"
VerticalAlignment="Top" />
<Label Content="PW"
Height="28"
HorizontalAlignment="Left"
Margin="22,70,0,0"
Name="label2"
VerticalAlignment="Top" />
<Button Content="OK"
Height="23"
HorizontalAlignment="Left"
Margin="70,119,0,0"
Name="button1"
VerticalAlignment="Top"
Width="120"
Command="{Binding Path=LoginCommand}"
CommandParameter="{Binding Path=.}"
/>
</Grid>
</Window>
(Ignore the Width, Height, Margin values, these are just copied & pasted from my designer and were quick & dirty adjusted to roughly look like your screenshot ;-) )
3) Create the Command class that will handle your log in logic. Note that I did not implement it as a RelayCommand like in Josh Smith's tutorial but it would be easy to modify the code accordingly:
namespace WpfApplication
{
using System;
using System.Windows;
using System.Windows.Input;
/// <summary>
/// Checks the user credentials.
/// </summary>
public class LoginCommand : ICommand
{
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
public void Execute(object parameter)
{
MainWindowViewModel viewModel = parameter as MainWindowViewModel;
if (viewModel == null)
{
return;
}
if (viewModel.Name == "ep" && viewModel.Password == "pass")
{
MessageBox.Show("namd and Pw accepted");
//open new page
var HomeScreen = new HomeScreen();
HomeScreen.Show();
}
else
{
//deny access
MessageBox.Show("Incorrect username and password");
}
}
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
public bool CanExecute(object parameter)
{
// Update this for your application's needs.
return true;
}
public event EventHandler CanExecuteChanged;
}
}
4) Now add the ViewModel that will communicate with the View and provide it the command interface and values:
namespace WpfApplication
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Input;
/// <summary>
/// TODO: Update summary.
/// </summary>
public class MainWindowViewModel : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Signal that the property value with the specified name has changed.
/// </summary>
/// <param name="propertyName">The name of the changed property.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion Implementation of INotifyPropertyChanged
#region Backing Fields
/// <summary>
/// Gets or sets the value of Name.
/// </summary>
private string name;
/// <summary>
/// Gets or sets the value of Password.
/// </summary>
private string password;
/// <summary>
/// Gets or sets the value of LoginCommand.
/// </summary>
private LoginCommand loginCommand;
#endregion Backing Fields
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="MainWindowViewModel"/> class.
/// </summary>
public MainWindowViewModel()
{
this.loginCommand = new LoginCommand();
}
#endregion Constructor
#region Properties
/// <summary>
/// Gets or sets the name of the user.
/// </summary>
public string Name
{
get
{
return this.name;
}
set
{
if (this.name == value)
{
return;
}
this.name = value;
this.OnPropertyChanged("Name");
}
}
/// <summary>
/// Gets or sets the user password.
/// </summary>
public string Password
{
get
{
return this.password;
}
set
{
if (this.password == value)
{
return;
}
this.password = value;
this.OnPropertyChanged("Password");
}
}
/// <summary>
/// Gets or sets the command object that handles the login.
/// </summary>
public ICommand LoginCommand
{
get
{
return this.loginCommand;
}
set
{
if (this.loginCommand == value)
{
return;
}
this.loginCommand = (LoginCommand)value;
this.OnPropertyChanged("LoginCommand");
}
}
#endregion Properties
}
}
5) Finally, do not forget to add an additional Window HomeScreen that will be opened by the LoginCommand to the solution. :-)
To implement that, you should bind the button's command like this -
<Button Content ="OK" Command = {Binding OKCommand} />
In your ViewModel, create an ICommand property for this binding like this -
public class MyViewModel() : INotifyPropertyChanged
{
ICommand _OKCommand;
public ICommand OKCommad
{
get { return _OKCommand; }
set { _OKCommand = value; PropertyChanged(OKCommad); }
}
public MyViewModel()
{
this.OKCommand += new DelegateCommand(OKCommand_Execute);
}
public void OKCommand_Execute()
{
// Code for button click here
}
}
Also note that for using this delegate command, you need to add reference to Microsoft.Practices.Prism.dll

Creating user defined dialogs in MVVM using Catel

i need to create a GUI-DLL-Component which should be used as a dialog.
This dialog performs some calculations and then searches the database for the result (let's say the result is a number).
The result is bound to the view via a public property of the viewmodel.
The user want to intantiate an object of this GUI component and open the dialog, after the calculation is done the user need to access the result in a later point of time.
What i want to ask is how to access the (Result) public property of the viewmodel after intantiating the object because i don't know how to do it in a MVVM way. My temproral solution is to cast the data context of the window in code behind and then access its public property. But it's not MVVM (In this case the dialog is derived from window class. And after the method .showdialog() is called there is no way to access the public property of the window's viewmodel).
How can i do this in a MVVM manner?
Thank you very much for your help :).
Best regards,
Minh
Edit:
here is my code:
XAML:
<catel:DataWindow x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodel="clr-namespace:WpfApplication3.ViewModels"
d:DesignHeight="273"
d:DesignWidth="457"
SizeToContent="WidthAndHeight">
<Window.DataContext>
<viewmodel:MainWindowViewModel></viewmodel:MainWindowViewModel>
</Window.DataContext>
<Grid>
<Button Content="Calc 1+1"
Height="39"
Name="button1"
Width="87"
Command="{Binding CalcCmd}"/>
<TextBox Height="23"
HorizontalAlignment="Left"
Name="textBox1"
VerticalAlignment="Top"
Width="87"
Margin="174,152,0,0"
Text="{Binding Result}"/>
<Label Content="Result:"
Height="28"
HorizontalAlignment="Left"
Margin="111,152,0,0"
Name="label1"
VerticalAlignment="Top"
Width="46" />
</Grid>
</catel:DataWindow>
Code Behind:
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Catel.Windows;
using WpfApplication3.ViewModels;
namespace WpfApplication3
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : DataWindow
{
public MainWindow()
{
InitializeComponent();
}
public MainWindow(MainWindowViewModel mainWindowViewModel)
: base(mainWindowViewModel)
{
InitializeComponent();
}
//Temporal solution
public string Result
{
get {
MainWindowViewModel vm = (MainWindowViewModel)this.DataContext;
return vm.Result;
}
}
}
}
ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Catel.MVVM;
using Catel.Data;
namespace WpfApplication3.ViewModels
{
/// <summary>
/// name view model.
/// </summary>
public class MainWindowViewModel : ViewModelBase
{
#region Fields
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="MainWindowViewModel"/> class.
/// </summary>
public MainWindowViewModel()
{
registeringCommands();
}
#endregion
#region Properties
/// <summary>
/// Gets the title of the view model.
/// </summary>
/// <value>The title.</value>
public override string Title { get { return "MyMainWindow"; } }
/// <summary>
/// Gets or sets the property value.
/// </summary>
public string Result
{
get { return GetValue<string>(ResultProperty); }
set { SetValue(ResultProperty, value); }
}
/// <summary>
/// Register the Result property so it is known in the class.
/// </summary>
public static readonly PropertyData ResultProperty =
RegisterProperty("Result", typeof(string), null);
#endregion
#region Commands
/// <summary>
/// Gets the name command.
/// </summary>
public Command CalcCmd { get; private set; }
/// <summary>
/// Method to invoke when the name command is executed.
/// </summary>
private void execute_CalcCmd()
{
try {
Result = (1 + 1).ToString();
}
catch(Exception ex)
{
throw;
//log
}
}
#endregion
#region Methods
private void registeringCommands()
{
CalcCmd = new Command(execute_CalcCmd);
}
#endregion
}
}
You can pass a view model instance to the ShowDialog method of the UIVisualizerService. This view model will still be available after the window is closed and this is the same view model used on the DataWindow. You can simply use the value in the calling view model.
If the result needs to be used in a lot of classes, it is better to create a dedicated class / service for this and register it in the ServiceLocator. For example, a Settings : ISettings which you modify in the DataWindow and you can read anywhere by querying the ServiceLocator / DependencyResolver or by letting them get injected in the classes where you need the information.

Categories