Software engineering and GUI design
Data Binding / False MVVM XAML extras, resources Creating additional windows
Lists (ItemsControl)
MVVM – Connection
Connect the control’s property with a property of the VM:
<TextBox Text=“{Binding Path=Income}” ... />
1. Source object DataContext One specific VM instance (unique source is possible: other control, template
, …
)2. Source property Income VM.Income (public property) 3. Dest. object TextBox (Dependency object)
4. Dest. property TextBox.Text (Dependency property) 5. Mode (OneWay, TwoWay, OneWayToSource, OneTime) 6. Update source (Explicit, PropertyChanged, LostFocus)
7. Converter (If we the source/dest property have different types)
View (XAML)
View- Model
Model (BL)
DATA BINDING
2
MVVM – The data comes from
„somewhere”
• In XAML we connect the property of a control with a property of the VM, the getter/setter of the VM are called automatically
• The VM can be created from C# or from XAML, we prefer the latter
• Important philosophy: 1 XAML node = create instance / set property value
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
View (XAML)
View- Model
Model (BL)
DATA BINDING
3
„Fake” MVVM: business logic in the xaml.cs
View (XAML)
View- Model
xaml.cs (BL?)
• Access VM created in the XAML
ViewModel VM = this.DataContext as ViewModel;
• Execute calculation in an event handler (xaml.cs)
VM.Result = VM.Income * VM.TaxPct / 100 - VM.Prepaid;
NO NAMES – THE LOGIC KNOWS NOTHING OF THE UI!
READ VM WRITE VM
DATA BINDING
UI EVENT
4
XAML markup extensions
• Using this syntax, we can specify complex content into one single attribute: using string, we create a simple new object
• Usage cases:
– Null value (Content="{x:Null}“)
– Associate content with an already existing instance/static class/property
– Create instance with a non-default constructor – Data binding! “Take the value from another class”
– etc.
• Format:
• Every markup extension has its own syntax/attributes
5
<SomeTag SomeAttribute="{…}"
XAML namespaces
• Namespaces define the allowed tags and attributes
• The one without the prefix (xmlns) allows us to use the tags defined in the referenced namespace
without any prefix (e.g. Window, Grid...)
• The one with prefix (xmlns:x) allows us to use the tags defined in the referenced namespace with the specified prefix (x:Name, x:FieldModifier)
6
<Window x:Class="WpfApplication4.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>
</Grid>
</Window>
XAML namespaces
• This imports the most basic WPF-centered .NET namespaces into the XAML:
– System.Windows
– System.Windows.Controls – System.Windows.Data – System.Windows.Media
– System.Windows.Navigation ... etc.
• No need for prefixes in tags/attributes
7
<Window x:Class="WpfApplication4.MainWindow”
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation
"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
...
<Window x:Class="WpfApplication4.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></Grid>
</Window>
XAML namespaces
• This imports the XAML-specific keywords and some of the types in System.Windows.Markup :
– Class, Null, Static, Array, ClassModifier, FieldModifier, DynamicResource, StaticResource, Key, Name, Code, …
• Only a few of them will be used, the prefix is usually x
• Name vs x:Name; “See also AutomationProperties.Name VS x:Name, AutomationProperties.Name is used by accessibility tools and some testing tools.”
8
<Window x:Class="WpfApplication4.MainWindow”
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation
"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
...
<Window x:Class="WpfApplication4.MainWindow”
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Button Content="START!" Name="buttonStart”
x:FieldModifier="public"/>
</Window>
XAML namespaces
• We can import any arbitrary .NET namespace
– clr-namespace:name_of_namespace NO SPACES ALLOWED!!!
– assembly: Typically DLL/EXE file that contains the class (without the extension) – refer to the manual
9
<Window x:Class="WpfApplication4.MainWindow"
...
xmlns:System="clr-namespace:System;assembly=mscorlib”>
<x:Array Type="System:String">
<System:String>Hello</System:String>
<System:String>World</System:String>
</x:Array>
</Window>
XAML
• Attached Property Syntax
10
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="107*"/>
<RowDefinition Height="115*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="66*"/>
<ColumnDefinition Width="67*"/>
</Grid.ColumnDefinitions>
<Button Content="Button1" Grid.Column="0" Grid.Row="0"/>
<Button Content="Button2" Grid.Column="1" Grid.Row="0"/>
<Button Content="Button3" Grid.Column="0" Grid.Row="1"/>
</Grid>
Resources
• A resource is a class/instance that we can reuse in our application multiple times
• Types:
– Logical/class resources: arbitrary instances (created in XAML) – Binary resources: images, icons, lookup tables...
• The logical resources are stored in the resource
dictionary, each of them has a unique name (x:Key)
– Resources property (in FrameworkElement descendants)
• Contained controls will receive the parent controls’ resources
<Window ...
<Grid>
<Grid.Resources>
<current:AgeValueConverter x:Key=“AgeConverter"/>
</Grid.Resources>
...
</Grid>
</Window>
Import the class into the resource dictionary of the Grid
11
Resources
• Logical/class resources can be used in other places:
– Brushes, colors, styles, templates, converters – Arbitrary instances (e.g. arrays)
– E.g.: color (brush) in the resource dictionary:
• Types:
– StaticResource: created once when the XAML is loaded, read-only – DynamicResource: loaded in run-time, it can be modified
<Window ...>
<Grid>
<Grid.Resources>
<SolidColorBrush x:Key=“MyColor" Color="Azure"/>
</Grid.Resources>
...
<Label Background="{StaticResource ResourceKey=MyColor}"
Content=“Text with nice color..." .../>
<ListBox Background="{StaticResource ResourceKey=MyColor}" .../>
</Grid>
</Window>
12
<Image
Source="LockScreen___0600_0337.jpg" />
Resources
• Binary resources:
– The project can include arbitrary files that the application needs
– To include a file as a binary resource, it has to be added to the project, then in the properties window we have to
change the “Build Action” of the file
• Resource: the compiler will embed the data into the .NET assembly
• Content: the files will remain external files, we have to change the “copy to output directory” setting as welll
• We can reference them in the XAML with their filename
13
Converters
• Data conversion:
– Can be automatic!
• E.g.: SolidColorBrush string, int string
– Otherwise, we have to use a class that implements the IValueConverter interface (System.Windows.Data)
– Two methods: Convert(), ConvertBack()
• Convert(): sourcetarget (VM UI)
• ConvertBack(): targetsource (UIVM)
• After the ConvertBack(), the getter is executed (along with the Convert())
<ListBox x:Name=“lbColors" BorderBrush=
"{Binding ElementName=lbColors, Path=SelectedItem}”
.../>
14
Converters
• Person class:
– Name (string) – Age (int)
• Binding: pesron.Age label.Content
<Window ...
xmlns:current="clr-namespace:WpfApplication9”
... >
<Grid>
<Grid.Resources>
<current:AgeValueConverter x:Key=“AgeConverter"/>
</Grid.Resources>
<Label Content=“Name of the person:" .../>
<Label Content=“Approximate age:" .../>
<Label Content="{Binding Name}” .../>
<Label Content="{Binding Age, Converter={StaticResource AgeConverter}}” ... />
</Grid>
</Window>
Reference the .NET namespace Import the .NET converter class into the XAML
Assign the converter to the binding
15
Converters
• Converter class
– In this form, only one-way conversion is required – Look out for the data types!
class AgeValueConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int age = (int)value;
if (age < 18)
return "child";
else if (age < 30) return "young";
else if (age < 50)
return "middle-aged";
else if (age < 65) return "older";
else
return "old";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
} }
16
MultiBinding+MultiValueConverter (EXTRA)
17
MultiBinding+MultiValueConverter (EXTRA)
18
19
Creating additional windows
• Two ways to display a new window:
– Modal windows: “always on top”, the user cannot switch to another window (warning/error/confirmation or data input) – Non-modal windows: other windows can be used meanwhile
the new window is open (special case: MDI)
• Possibilities:
– MessageBox class (only modal)
– Microsoft.Win32 dialog windows (only modal)
– Inheriting from the Window class (modal or non-modal) – Some old Windows Forms dialog windows are not in WPF:
FolderBrowserDialog (WHY???), ColorDialog, FontDialog, PageSetupDialog, PrintPreviewDialog (not so big problem + conflicting types)
20
MessageBox.Show()
MessageBoxResult result =
MessageBox.Show("Multiline\nmessage\nheader",
"Header", MessageBoxButton.YesNoCancel, MessageBoxImage.Error);
• Minimum 1, maximum 4 parameters:
MessageBoxButton OK
OKCancel YesNo
YesNoCancel
Microsoft.Win32 dialog windows
• OpenFileDialog, SaveFileDialog
• A ShowDialog() return value:
– bool? type (nullable bool)
– true: the user successfully completed the interaction (the values can be used)
– false: „cancel” type exit (the values cannot be used) – null: the window is still open
• DIFFERENT than in Windows Forms! We must ALWAYS check for this value!
21
OpenFileDialog openFileDialog = new OpenFileDialog();
if (openFileDialog.ShowDialog() == true) {
string fileName = openFileDialog.FileName;
//... Open file, read file ...
}
22
Inheriting from the Window class
• Creating our own custom windows:
1. .xaml + .xaml.cs files are required:
• Project -> Add -> Window...
• Same usage as our first window: use the designer, edit properties, add event handlers ...
2. Create an instance, then use Show() or ShowDialog()
• Show?
– window.Show(); non modal, non-blocking method call
– window.ShowDialog(); modal, with a bool? return value;
this is a BLOCKING method call!
MyWindow window = new MyWindow();
window.Show();
MyWindow window = new MyWindow();
if (window.ShowDialog() == true) {
//...
}
Inheriting from the Window class
• To give information after a ShowDialog() call, we have to set the this.DialogResult property within the new window
– This will also instantly close the new window
• Button controls have the following related properties:
– IsCancel – This button will be “clicked” when pressing the ESC key, this will also close the window
– IsDefault – This button will be “clicked” when pressing the ENTER/RETURN key
• Will not close the window
• Usually we set the DialogResult in the event handler
23
private void Button_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
}
24
Inheriting from the Window class
• To get data from a modal window, we use properties
– Create one property for every data field – Don’t access controls directly!
– x:Name="myTextBox" x:FieldModifier="private"
• In the activator window, we first check the dialog result and then we use the property values
string name;
string birthPlace;
int age;
PersonalDataWindow dataInput = new PersonalDataWindow();
if (dataInput.ShowDialog() == true) {
name = dataInput.Name;
birthPlace = dataInput.BirthPlace;
age = dataInput.Age;
}
Exercise
25
Practice exercise
26
Extra Exercise
27