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
Related
I have to validate some Properties, before I try to save my Entity into Database.
Problem:
Programmstart: Validation error is shown of textbox in tabitem1
User select: Tabitem2 in the View
User select: Tabitem1 in the View
Validation error of empty textbox is not shown in tabitem1 anymore.
Excepted behavior:
Validation error shall be shown every time also if the user changes the selected tabitem.
Tools / Frameworks used:
Prism 6.3 (New Project with Templatepack PrismUnity
Prism.Validation (https://github.com/mfe-/Prism.Validation)
Questions:
Why the DataAnnoation is not shown anymore after selection between the different tabitems? The ViewModel property hasErrors is true.
How I can restart the evaluation, if the user selected tabitem1 again?
View:
<Window x:Class="PrismUnityApp1TestValidation.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="{Binding Title}" Height="350" Width="525">
<StackPanel>
<!--<ContentControl prism:RegionManager.RegionName="ContentRegion" />-->
<TabControl>
<TabItem>
<TabItem.Content>
<TextBox Height="50" Text="{Binding TestText, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>
</TabItem.Content>
</TabItem>
<TabItem>
<TabItem.Content>
<TextBlock Text="TabItem2"></TextBlock>
</TabItem.Content>
</TabItem>
</TabControl>
</StackPanel>
</Window>
ViewModel:
using System.ComponentModel.DataAnnotations;
using Prism.Mvvm;
using Prism.Validation;
namespace PrismUnityApp1TestValidation.ViewModels
{
public class MainWindowViewModel : ValidatableBindableBase
{
private string _title = "Prism Unity Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private string _testtext;
[Required]
public string TestText
{
get { return _testtext; }
set { SetProperty(ref _testtext, value); }
}
public MainWindowViewModel()
{
}
}
}
ValidatableBindableBase (NugetPackage Prism.Validation):
namespace Prism.Validation
{
/// <summary>
/// The IValidatableBindableBase interface was created to add validation support for model classes that contain validation rules.
/// The default implementation of IValidatableBindableBase is the ValidatableBindableBase class, which contains the logic to run the validation rules of the
/// instance of a model class and return the results of this validation as a list of properties' errors.
/// </summary>
// Documentation on validating user input is at http://go.microsoft.com/fwlink/?LinkID=288817&clcid=0x409
public class ValidatableBindableBase : BindableBase, IValidatableBindableBase, INotifyDataErrorInfo
{
private readonly BindableValidator _bindableValidator;
/// <summary>
/// Initializes a new instance of the <see cref="ValidatableBindableBase"/> class.
/// </summary>
public ValidatableBindableBase()
{
_bindableValidator = new BindableValidator(this);
}
/// <summary>
/// Gets or sets a value indicating whether this instance is validation enabled.
/// </summary>
/// <value>
/// <c>true</c> if validation is enabled for this instance; otherwise, <c>false</c>.
/// </value>
public bool IsValidationEnabled
{
get { return _bindableValidator.IsValidationEnabled; }
set { _bindableValidator.IsValidationEnabled = value; }
}
/// <summary>
/// Returns the BindableValidator instance that has an indexer property.
/// </summary>
/// <value>
/// The Bindable Validator Indexer property.
/// </value>
public BindableValidator Errors
{
get
{
return _bindableValidator;
}
}
/// <summary>
/// Gets a value that indicates whether the entity has validation errors.
/// </summary>
/// <value>
/// <c>true</c> if this instance contains validation errors; otherwise, <c>false</c>.
/// </value>
public bool HasErrors
{
get
{
return !ValidateProperties();
}
}
/// <summary>
/// Occurs when the Errors collection changed because new errors were added or old errors were fixed.
/// </summary>
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged
{
add { _bindableValidator.ErrorsChanged += value; }
remove { _bindableValidator.ErrorsChanged -= value; }
}
/// <summary>
/// Gets all errors.
/// </summary>
/// <returns> A ReadOnlyDictionary that's key is a property name and the value is a ReadOnlyCollection of the error strings.</returns>
public ReadOnlyDictionary<string, ReadOnlyCollection<string>> GetAllErrors()
{
return _bindableValidator.GetAllErrors();
}
/// <summary>
/// Validates the properties of the current instance.
/// </summary>
/// <returns>
/// Returns <c>true</c> if all properties pass the validation rules; otherwise, false.
/// </returns>
public bool ValidateProperties()
{
return _bindableValidator.ValidateProperties();
}
/// <summary>
/// Validates a single property with the given name of the current instance.
/// </summary>
/// <param name="propertyName">The property to be validated.</param>
/// <returns>Returns <c>true</c> if the property passes the validation rules; otherwise, false.</returns>
public bool ValidateProperty(string propertyName)
{
return !_bindableValidator.IsValidationEnabled // don't fail if validation is disabled
|| _bindableValidator.ValidateProperty(propertyName);
}
/// <summary>
/// Sets the error collection of this instance.
/// </summary>
/// <param name="entityErrors">The entity errors.</param>
public void SetAllErrors(IDictionary<string, ReadOnlyCollection<string>> entityErrors)
{
_bindableValidator.SetAllErrors(entityErrors);
}
/// <summary>
/// Checks if a property already matches a desired value. Sets the property and
/// notifies listeners only when necessary. We are overriding this property to ensure that the SetProperty and the ValidateProperty methods are fired in a
/// deterministic way.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to a property with both getter and setter.</param>
/// <param name="value">Desired value for the property.</param>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers that
/// support CallerMemberName.</param>
/// <returns>
/// True if the value was changed, false if the existing value matched the
/// desired value.
/// </returns>
protected override bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
var result = base.SetProperty(ref storage, value, propertyName);
if (result && !string.IsNullOrEmpty(propertyName))
{
if (_bindableValidator.IsValidationEnabled)
{
_bindableValidator.ValidateProperty(propertyName);
}
}
return result;
}
/// <summary>
/// Gets the validation errors for a specified property or for the entire entity.
/// </summary>
/// <param name="propertyName">The name of the property to retrieve validation errors for; or null or Empty, to retrieve entity-level errors.</param>
/// <returns>The validation errors for the property or entity.</returns>
public IEnumerable GetErrors(string propertyName)
{
if (HasErrors==false)
{
return Enumerable.Empty<String>();
}
return _bindableValidator[propertyName];
}
}
}
I found a solution here:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/fb50537c-feec-42dc-8439-dcf78ef8951a/validation-error-and-tab-control?forum=wpf
I changed as mentioned in the post my View.
View:
<Window x:Class="PrismUnityApp1TestValidation.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="{Binding Title}" Height="350" Width="525">
<StackPanel>
<!--<ContentControl prism:RegionManager.RegionName="ContentRegion" />-->
<TabControl>
<TabItem IsSelected="{Binding TabItem1Selected}">
<TabItem.Content>
<AdornerDecorator>
<TextBox Height="50" Text="{Binding TestText, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>
</AdornerDecorator>
</TabItem.Content>
</TabItem>
<TabItem>
<TabItem.Content>
<TextBlock Text="TabItem2"></TextBlock>
</TabItem.Content>
</TabItem>
</TabControl>
</StackPanel>
</Window>
I have a ListBox with my menu items in it.
<ListBox x:Name="ListBoxMenu" SelectionChanged="ListBoxMenu_SelectionChanged"
Grid.Column="0" Margin="0" Padding="0" Grid.Row="1" Width="{StaticResource LeftMenuWidth}"
ItemsSource="{Binding MenuItems}"
Background="{StaticResource ListBoxColor}"
BorderThickness="0"
SelectedIndex="0" VerticalAlignment="Stretch" >
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<Image Source="{Binding MenuImage}" Height="20" Width="20" DockPanel.Dock="Left" Margin="5" />
<TextBlock Text="{Binding MenuName}" FontSize="{StaticResource MenuFontSize}" FontWeight="Bold" DockPanel.Dock="Left" Width="Auto" VerticalAlignment="Center" HorizontalAlignment="Left"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I kind of chopped up the code hope it still make since.
Then I have a control template that loads each user control.
<ContentControl Content="{Binding ElementName=ListBoxMenu, Path=SelectedItem}" Grid.Column="1" Grid.Row="1"/>
The Problem:
My problem is that I would like to test when a user leaves the user control if they have made any changes to ask them to save the changes. I already have NotifyPropertyChange working so that isn't the problem. I need to figure out how to see when the user is leaving the control / page.
What I have tried
As you can see I have added selectionchanged to the list box which technically does work however its not ideal because the usercontrol changes visually then the user is prompted to save any changes. I want to prompt them before they leave the user control.
SelectionChanged="ListBoxMenu_SelectionChanged"
Updates #1
There is more than one possible helpful suggestions to handle the "from view navigation", here are a couple of simple examples:
In order to check if your control is active(I mean is shown to user), when there is no any navigation controllers in use, I think you can use the control's IsVisibleChanged event that indicates the control's IsVisible(true/false) state. In case you want to start the IsDirty logic when your control is partially visible you can use the #Evk guy suggestion(using of LostFocus), test the IsHitTestVisible on the control bounds and according to the test result(how more the control is hidden) you can start(or not) your desired logic.
Here is an example of IsHitTest visibilty(from this link)
/// <summary>
/// helps to indicate the partial IsVisible state
/// </summary>
/// <param name="element">elemnt under the test</param>
/// <param name="container">parent window or control</param>
/// <returns></returns>
private bool IsUserVisible(FrameworkElement element, FrameworkElement container)
{
if (!element.IsVisible)
return false;
Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);
}
In case you have a navigation supporting control(something like a Frame) you can use its events to know that the navigation is started(aka you are going to move to another control), like FragmentNavigation.
In addition you should implement an IsDirty on your ViewModel. Here are a few examples of how to do that:
MVVM - implementing 'IsDirty' functionality to a ModelView in order to save data
Almost-automatic INotifyPropertyChanged, automatic IsDirty, and automatic ChangeTracking.
Here is some code sample for an IsDirty implementation(all credit to this guy)
/// <summary>
/// Provides a base class for objects that support property change notification
/// and querying for changes and resetting of the changed status.
/// </summary>
public abstract class ViewModelBase : IChangeTracking, INotifyPropertyChanged
{
//========================================================
// Constructors
//========================================================
#region ViewModelBase()
/// <summary>
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
/// </summary>
protected ViewModelBase()
{
this.PropertyChanged += new PropertyChangedEventHandler(OnNotifiedOfPropertyChanged);
}
#endregion
//========================================================
// Private Methods
//========================================================
#region OnNotifiedOfPropertyChanged(object sender, PropertyChangedEventArgs e)
/// <summary>
/// Handles the <see cref="INotifyPropertyChanged.PropertyChanged"/> event for this object.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">A <see cref="PropertyChangedEventArgs"/> that contains the event data.</param>
private void OnNotifiedOfPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e != null && !String.Equals(e.PropertyName, "IsChanged", StringComparison.Ordinal))
{
this.IsChanged = true;
}
}
#endregion
//========================================================
// IChangeTracking Implementation
//========================================================
#region IsChanged
/// <summary>
/// Gets the object's changed status.
/// </summary>
/// <value>
/// <see langword="true"/> if the object’s content has changed since the last call to <see cref="AcceptChanges()"/>; otherwise, <see langword="false"/>.
/// The initial value is <see langword="false"/>.
/// </value>
public bool IsChanged
{
get
{
lock (_notifyingObjectIsChangedSyncRoot)
{
return _notifyingObjectIsChanged;
}
}
protected set
{
lock (_notifyingObjectIsChangedSyncRoot)
{
if (!Boolean.Equals(_notifyingObjectIsChanged, value))
{
_notifyingObjectIsChanged = value;
this.OnPropertyChanged("IsChanged");
}
}
}
}
private bool _notifyingObjectIsChanged;
private readonly object _notifyingObjectIsChangedSyncRoot = new Object();
#endregion
#region AcceptChanges()
/// <summary>
/// Resets the object’s state to unchanged by accepting the modifications.
/// </summary>
public void AcceptChanges()
{
this.IsChanged = false;
}
#endregion
//========================================================
// INotifyPropertyChanged Implementation
//========================================================
#region PropertyChanged
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region OnPropertyChanged(PropertyChangedEventArgs e)
/// <summary>
/// Raises the <see cref="INotifyPropertyChanged.PropertyChanged"/> event.
/// </summary>
/// <param name="e">A <see cref="PropertyChangedEventArgs"/> that provides data for the event.</param>
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
#endregion
#region OnPropertyChanged(string propertyName)
/// <summary>
/// Raises the <see cref="INotifyPropertyChanged.PropertyChanged"/> event for the specified <paramref name="propertyName"/>.
/// </summary>
/// <param name="propertyName">The <see cref="MemberInfo.Name"/> of the property whose value has changed.</param>
protected void OnPropertyChanged(string propertyName)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
#endregion
#region OnPropertyChanged(params string[] propertyNames)
/// <summary>
/// Raises the <see cref="INotifyPropertyChanged.PropertyChanged"/> event for the specified <paramref name="propertyNames"/>.
/// </summary>
/// <param name="propertyNames">An <see cref="Array"/> of <see cref="String"/> objects that contains the names of the properties whose values have changed.</param>
/// <exception cref="ArgumentNullException">The <paramref name="propertyNames"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
protected void OnPropertyChanged(params string[] propertyNames)
{
if (propertyNames == null)
{
throw new ArgumentNullException("propertyNames");
}
foreach (var propertyName in propertyNames)
{
this.OnPropertyChanged(propertyName);
}
}
#endregion
}
Let me know if you want more examples or code.
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.
I have a simple WPF app with 3 textboxes, 2 of the text boxes input numbers and the third textbox shows the sum of inputs when another button is clicked.
I come from WinForms and MFC background and for me, the intuitive thing to do is to right click the textBoxes, open their properties and specify local variables to read the data from the boxes. For example, MFC has the DDX mechanism for this.
However, in WPF, the only way to specify a binding seems to add XAML code directly to App.XAML, as shown here on MSDN. Is there a way to create a binding without coding it manually into XAML? XAML coding seems a little daunting to me, since I am new to it.
My WPF form is as follows :
<Window x:Class="SimpleAdd.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox HorizontalAlignment="Left" Height="23" Margin="174,43,0,0" TextWrapping="Wrap" Text="{Binding dataModel.Value1}" VerticalAlignment="Top" Width="120"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="174,84,0,0" TextWrapping="Wrap" Text="{Binding dataModel.Value2}" VerticalAlignment="Top" Width="120"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="174,127,0,0" TextWrapping="Wrap" Text="{Binding dataModel.Value3}" VerticalAlignment="Top" Width="120"/>
<Button Content="Add" HorizontalAlignment="Left" Margin="393,84,0,0" VerticalAlignment="Top" Width="75" Click="OnAdd"/>
</Grid>
</Window>
My C# file is as follows :
namespace SimpleAdd
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnAdd(object sender, RoutedEventArgs e)
{
dataModel m1 = new dataModel();
m1.Value3 = m1.Value1 + m1.Value2; // BUG : All Properties are 0 even after updating the boxes.
}
}
public class dataModel
{
private int val1, val2, val3;
public int Value1
{
get {return val1;}
set { val1 = value; }
}
public int Value2
{
get { return val2; }
set { val2 = value; }
}
public int Value3
{
get { return val3; }
set { val3 = value; }
}
}
}
EDIT : Adding implementation for INotifyPropertyChanged
namespace SimpleAdd
{
public abstract class ObservableObject : INotifyPropertyChanged
{
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public virtual void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
#region INotifyPropertyChanged Members
/// <summary>
/// Raises the PropertyChange event for the property specified
/// </summary>
/// <param name="propertyName">Property name to update. Is case-sensitive.</param>
public virtual void RaisePropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
OnPropertyChanged(propertyName);
}
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
}
}
Your TextBox are not being updated because you haven't set the data source (DataContext typically) behind the bindings.
When you write
<TextBox Text="{Binding dataModel.Value1}" />
What you are really saying "pull the value for this field from TextBox.DataContext.dataModel.Value1". If TextBox.DataContext is null, then nothing will be displayed.
The DataContext is inherited automatically, so the following code would work :
public partial class MainWindow : Window
{
public dataModel _data { get; set; }
public MainWindow()
{
InitializeComponent();
_data = new dataModel();
this.DataContext = _data;
}
private void OnAdd(object sender, RoutedEventArgs e)
{
_data.Value3 = _data.Value1 + _data.Value2;
}
}
assuming you also change your TextBox bindings to remove the dataModel. from them
<TextBox Text="{Binding Value1}" />
This sets the DataContext of the entire form to the _data object, and in your OnAdd method we can update the _data object properties in order to have the UI update.
I like to blog a bit about beginner WPF stuff, and you may be interested in checking out a couple of the posts there which explain these concepts :
Understanding the change in mindset when switching from WinForms to WPF
What is this "DataContext" you speak of?
Technically, that's not in App.xaml (which is a special file in WPF).
That said, yes you can do it. You can set up the binding in code like so:
textBox1.Text = new Binding("SomeProperty");
...
Okay, that gets really annoying so we just do it in XAML:
<TextBox Text="{Binding SomeProperty}"/>
Both pieces of code do the same thing, but when you get into more advanced bindings, the XAML syntax is a lot easier to use. Plus, its more obvious where you text is coming from rather than having to open two files.
The FrameworkElement class and the FrameworkContentElement class both expose a SetBinding method. If you are binding an element that inherits either of these classes, you can call the SetBinding method directly.
The following example creates a class named, MyData, which contains a property named MyDataProperty.
public class MyData : INotifyPropertyChanged
{
private string myDataProperty;
public MyData() { }
public MyData(DateTime dateTime)
{
myDataProperty = "Last bound time was " + dateTime.ToLongTimeString();
}
public String MyDataProperty
{
get { return myDataProperty; }
set
{
myDataProperty = value;
OnPropertyChanged("MyDataProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
}
The following example shows how to create a binding object to set the source of the binding. The example uses SetBinding to bind the Text property of myText, which is a TextBlock control, to MyDataProperty.
MyData myDataObject = new MyData(DateTime.Now);
Binding myBinding = new Binding("MyDataProperty");
myBinding.Source = myDataObject;
myText.SetBinding(TextBlock.TextProperty, myBinding);
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.