ViewModels
Must implement INotifyPropertyChange
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _configurationFile;
void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
// Eliminates Race condition
var localPC = PropertyChanged;
if (localPC != null)
{
localPC(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Label Alignment
Use HorizontalContentAlignment & VerticalContentAlignment to align the text in a label
Prism, Commands & ListBox Selected Items
With ListBoxes you often need to perform an action on the list of selected items. This usually involves a Button being click to indicate – Execute Action.
Question is with MVVM how do you access the Selected Items? The ViewModel knows about the Collection that the list box is displaying, but that Collection object does not have a ‘SelectedItems’ property.
The answer is to add a CommandParameter to the Button pointing to the SelectedItems property of the ListBox. eg
<ListBox x:Name="MyCollectionLbx" ItemsSource="{Binding MyCollection}"
SelectionMode="Multiple"></ListBox>
<Button x:Name="DeleteItemsBtn"
Command="{Binding DeleteItemsCommand}"
CommandParameter="{Binding ElementName=MyCollectionLbx,
Path=SelectedItems}">Delete</Button>
This will pass the SelectedItems of ListBox MyCollectionLbx as an ICollection<object> to the CanExecute and Execute methods of the ICommand DeleteItemsCommand
However, should you be using Prism’s DelegateCommand then the question is how do you construct the delegate commands to accept CommandParameters? Answer:
DeleteItemsCommand = new DelegateCommand<ICollection<object>>
(ExecuteDeleteItems, CanExecuteDeleteItems);
public bool CanExecuteDeleteItems(ICollection<object> selectedItems)
{
return true;
}
public void ExecuteDeleteItems(ICollection<object> selectedItems)
{
// Action code here
IList<object> copy = selectedItems.ToList<object>();
foreach (var item in copy)
{
FrequencyList.Remove((double)item);
}
}
SelectedItems are returned as an ICollection<object>
Note: The SelectedItems are returned as an ICollection<object>, so you do have to cast the items to their Type, which is a shame.
Modifying the Original Collection, modifies the SelectedItems Collection
Any modification to the original collection in the Command Execute function (i.e. the ExecuteDeleteItems function above) will also change the selectedItems collection.
This is why, in the ExecuteDeleteItems function above, we first take a copy of the SelectedItems collection, and then iterate over the copy to delete the selected entries from the original collection.
Creating New Controls
WPF provicdes three mechanisms for creating New Controls
- Modify presentation and behaviour of standard control
- User Controls
- Custom Controls
Modify presentation and behaviour of standard control
User Controls
Example of a File Selection User Control.
The control provides for:
- A Label to indicate the purpose of the control
- A TextBox into which the user can enter a File path
- A Button to trigger a File Selection Dialog as a Model Dialogue window. Results of which are written to the TextBox
The behaviour we have is:
- The Label is designated the Content Proptery of the Control. Hence it is populated by <fsc>Content<fsc>
- The Textbox is bound to the FileName property and can be pre-populated via that Property and the its value will be returned to whatever is bound to that Property.
<UserControl x:Class="C_V_App.UserControls.FileSelectionControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:C_V_App.UserControls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:Name="FileSelectionUserControl">
<Grid Margin="0,2,0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label x:Name="FileDialogLbl" Grid.Column="0" MinWidth="190" HorizontalAlignment="Right" HorizontalContentAlignment="Right"/>
<TextBox x:Name="FileDialogTbx" Grid.Column="1" Margin="2,0,2,0" MinWidth="375"
VerticalContentAlignment="Center" HorizontalAlignment="Left"
Text="{Binding FileName, ElementName=FileSelectionUserControl, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>
<Button x:Name="FileDialogBtn" Grid.Column="2" Click="FileDialogBtn_OnClick" >Browse...</Button>
</Grid>
</UserControl>
Code-Behind. I realise that having code behind is deemed blasphomous by some. I agree for the most part, but for me it is not a matter of rigourously following dogma. My opinion is that the FileSelectionDialog displayed by the Browse… button is a UI Instrument for populating the TextBox. Its display and control is the responsibility of the View. It is most definitely not the responsibility of the ViewModel.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using Microsoft.Win32;
namespace C_V_App.UserControls
{
/// <summary>
/// Interaction logic for FileSelectionControl.xaml
/// </summary>
///
[ContentProperty ("Label")]
public partial class FileSelectionControl : UserControl
{
public static readonly DependencyProperty FileNameProperty =
DependencyProperty.Register("FileName", typeof(string), typeof(FileSelectionControl));
public event EventHandler<EventArgs> FileNameChanged;
public FileSelectionControl()
{
FileDialogue = new OpenFileDialog();
InitializeComponent();
FileDialogTbx.TextChanged += new TextChangedEventHandler(OnFileNameChanged);
}
private OpenFileDialog FileDialogue { get; set; }
public void FileDialogBtn_OnClick (object sender, RoutedEventArgs args)
{
if (FileDialogue.ShowDialog() == true)
{
FileName = FileDialogue.FileName;
}
}
public string Label
{
get { return this.FileDialogLbl.Content.ToString(); }
set { this.FileDialogLbl.Content = value; }
}
public string FileName
{
get { return (string)GetValue(FileNameProperty); }
set { SetValue(FileNameProperty, value); }
}
public void OnFileNameChanged (object sender, TextChangedEventArgs args)
{
var local = FileNameChanged;
if (local != null)
{
local(this, args);
}
}
}
}
Consuming User Control
<ucs:FileSelectionControl x:Name="KeithleyUsc"
FileName ="{Binding Path=KeithleyEmulationFile, Mode=TwoWay}">
Keithley2400 Emulation File:
</ucs:FileSelectionControl>
FileName is a TwoWay binding
Notice that FileName binding is two-way.
Notice the Label is derived from the controls content
Custom Controls
Status Bar
A Status bar is basically like a menu, (ie a container that accepts Separators), except its items are displayed horizontally.
Data Binding
Data Binding to a Property of an Object that is a Property of Data Source
The data binding syntax is one of those things where you just have to learn and remember it. You cannot (IMHO) decern what it should be from a logoical interptretation of lexical rules.
As an example (as the heading suggests), I have a Data Source, lets call it College. College has a Property call Principle and Principle has (at least) two Properties FirstName & LastName.
If I have a view whose DataContext is College, what is the syntax to Bind to the Principle’s First and Last Name. I could, simply expose these Properties as Properties of the College, and that would work, up to the point where the Principle, and hence her/his name changes.
What is the correct syntax to cover this:
<TextBox Text="{Binding Principle.FirstName}" />