WPF / XAML / C#
Delegate Command & Data-Binding
Firstly, create a WPF App of the .NET Framework within Visual studio:
Secondly, create your target objects, in my instance, I created a Label, of which's content is "Invisible Text", as well as a button who's content is "Shows Invisible Text". Center and align these on the view as you please. The code for the creation of these is as follows:
<Label Content="Invisible Text"/>
<Button Content="Shows Invisible Text" Height="50" Width="200"/>
Next, we must create our .cs (C# Class file), of which I have named 'ViewModelExample', as to hint that you should check out the MVVM pattern, see here.
Once this has been created, you can set the Data-Context within your XAML view to indicate that all elements bound within this view will have it's functionality stored in this class, do this as follows:
<Window.DataContext>
<local:ViewModelExample/>
</Window.DataContext>
<local:ViewModelExample/>
</Window.DataContext>
Where "local:", refers to a defined namespace ("xmlns" : xml namespace) in the top of your XAML window. Mine is as follows (it should be auto-set):
<Window x:Class="ExampleWPFApp.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:local="clr-namespace:ExampleWPFApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
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:local="clr-namespace:ExampleWPFApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
The next step is where it starts getting tricky. Add to the target objects in your MainView.xaml file (or one you're using), the bindings you wish to create. The objective I want from my program is to have 'OnClick' of the "Show Invisible Text" button, the invisible text to be shown. Thus I have to bind the Command of the button, and the Visibility of the label. My XAML code is updated as follows:
<Label Content="Invisible Text" Margin="0,41,0.4,-41.2" Visibility="{Binding LblInvisibleTextVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="Shows Invisible Text" Height="50" Width="200" Command="{Binding BtnToggleLblVisibilityDelegateCommand, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Note: These "LblInvisibleTextVisibility" and "BtnToggleLblVisibilityDelegateCommand" are going to be held within the Class file, but have not yet been implemented. If you try to run the code as-is, you will see "Binding Error" in the Visual Studio output - this means the 'binding source' cannot be found.
An explanation of the above syntax is as follows:
- {Binding LblInvisibleTextVisibility: Binding is the keyword used to indicate a binding between values is about to occur, and the LblInvisibleTextVisibility is the variable name that is is to be bound to (I just made up the name, it's a good idea to be as descriptive as possible, because these things can become confusing in huge programs!)
- Mode=TwoWay: "Causes changes to either the source property or the target property to automatically update the other. This type of binding is appropriate for editable forms or other fully-interactive UI scenarios" (MSDN, 2017).
- UpdateSourceTrigger=PropertyChanged: "Means that when a change occurs in the target, the source is updated immediately. For example, as you type characters in a
TextBox
which is the target of a binding, the source is updated, too. This means that theText
property of theTextBox
changes as you type characters" (CodeProject, 2017).
Next we must implement the Class file logic behind this functionality! Lets start with the binding to label visibility. We must invoke the System.Windows.Visibility enum which permits a Visibility status to be 'Hidden, Visible, Collapsed'. In order to ensure that it is bindable, this variable MUST have 'get' and 'set' functionality and MUST be 'public'. I have implemented a small feeder value to hold the variables value as it makes implementing an extension of this variable's internal methods simpler. The code is as follows:
#region Variables
public Visibility LblInvisibleTextVisibility
{
get => _lblInvisibleTextVisibility;
set
{
_lblInvisibleTextVisibility = value;
}
}
public Visibility LblInvisibleTextVisibility
{
get => _lblInvisibleTextVisibility;
set
{
_lblInvisibleTextVisibility = value;
}
}
//sub property used for holding values within the above monitored variable
private Visibility _lblInvisibleTextVisibility;
private Visibility _lblInvisibleTextVisibility;
#endregion
This is nearing completion, however it still does not 'notify' the view when the Visibility has changed value, i.e. a 'setting' of values has occurred, so the view will never know it has been updated, despite awaiting the notification of 'PropertyChanged'. To implement this, we must install the NuGet Prism 6 (latest available Nov 2017) package, as seen below:
We must then update our ViewModelExample C# class to implement INotifyPropertyChanged. We do this by updating our code FROM this:
public class ViewModelExample
{
TO this:
public class ViewModelExample : INotifyPropertyChanged
{
Visual Studio will then recognize you have not implemented the abstracted methods associated with this Prism class, and will offer to implement them for you; accept. Thus your class will have these methods attached:
#region Property Changed Logic -- autogenerated
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
Make use of this newly added functionality, by notifying the view when the value of the Visibility has been updated, by updating our Variable to this:
#region Variables
public Visibility LblInvisibleTextVisibility
{
get => _lblInvisibleTextVisibility;
set
{
OnPropertyChanged(nameof(LblInvisibleTextVisibility));
public Visibility LblInvisibleTextVisibility
{
get => _lblInvisibleTextVisibility;
set
{
OnPropertyChanged(nameof(LblInvisibleTextVisibility));
//we are notifying the view that the visibility has changed
_lblInvisibleTextVisibility = value;
}
}
private Visibility _lblInvisibleTextVisibility;
#endregion
_lblInvisibleTextVisibility = value;
}
}
private Visibility _lblInvisibleTextVisibility;
#endregion
Note: To avoid unexpected errors, create a constructor for the class and set the initial Visibility value to Visibility.Hidden within it!
Finally, the most challenging concept to understand; the DelegateCommand, essentially it is a reusable command of which when activated calls some function -- not hard right? It's very powerful. The DelegateCommand we will be implementing will be the BtNtoggleLblVisibilityDelegateCommand linked to the button on the MainView.xaml. Below is the code to create it (remembering to have a getter and setter):
public DelegateCommand BtnToggleLblVisibilityDelegateCommand { get; set; }
In the constructor, we initialize it fully and define which function its going to call, as follows:
BtnToggleLblVisibilityDelegateCommand = new DelegateCommand(ToggleVisibility);
Where 'ToggleVisibility' is the class we are going to create of which this DelegateCommand calls once activated - Simple!
The ToggleVisibility class I created is as follows and is purely logic:
private void ToggleVisibility()
{
if(LblInvisibleTextVisibility == Visibility.Visible)
{
LblInvisibleTextVisibility = Visibility.Hidden; //hide it
}
LblInvisibleTextVisibility = Visibility.Hidden; //hide it
}
else //if hidden
{
LblInvisibleTextVisibility = Visibility.Visible; //show it
}
{
LblInvisibleTextVisibility = Visibility.Visible; //show it
}
}
What will happen now?
- The XAML mainpage loads, looks at the Visibility and notices it has been set to 'Hidden' in the Constructor, the label is thus hidden.
- The user clicks the button, and the DelegateCommand is called, this in turn calls the ToggleVisibility function. It notices the Label's visibility is 'Hidden' and switches it to 'Visible'. This change in value gets routed through the 'OnPropertyChanged' in the Visibility setter and tells the view that something has changed in the label.
- The label looks at the status of it's visibility and notes it is now supposed to be visible, thus it reveals itself.
- And so on!
I've also created a YouTube follow-along if you'd like to watch this in practice and are more of a visual learner.
View it here:
Full Code is as Follows:
MAINWINDOW.XAML
<Window x:Class="ExampleWPFApp.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:local="clr-namespace:ExampleWPFApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<!-- We must set the data context of the application to reference the .CS viewmodelexample class -->
<Window.DataContext>
<local:ViewModelExample/>
</Window.DataContext>
<Grid>
<Label Content="Visible Text"/>
<!-- This visibility property will be 'bound' to a public variable in the viewmodelexample class, when it changes, the visibility of this label changes -->
<Label Content="Invisible Text" Margin="0,41,0.4,-41.2" Visibility="{Binding LblInvisibleTextVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<!-- On click of this button we want to toggle if the label is visible or not! we need a delegate command to do this -->
<Button Content="Shows Invisible Text" Height="50" Width="200" Command="{Binding BtnToggleLblVisibilityDelegateCommand, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
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:local="clr-namespace:ExampleWPFApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<!-- We must set the data context of the application to reference the .CS viewmodelexample class -->
<Window.DataContext>
<local:ViewModelExample/>
</Window.DataContext>
<Grid>
<Label Content="Visible Text"/>
<!-- This visibility property will be 'bound' to a public variable in the viewmodelexample class, when it changes, the visibility of this label changes -->
<Label Content="Invisible Text" Margin="0,41,0.4,-41.2" Visibility="{Binding LblInvisibleTextVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<!-- On click of this button we want to toggle if the label is visible or not! we need a delegate command to do this -->
<Button Content="Shows Invisible Text" Height="50" Width="200" Command="{Binding BtnToggleLblVisibilityDelegateCommand, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
VIEWMODELEXAMPLE.CS
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using ExampleWPFApp.Annotations;
using Prism.Commands;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using ExampleWPFApp.Annotations;
using Prism.Commands;
namespace ExampleWPFApp
{
public class ViewModelExample : INotifyPropertyChanged
{
#region Variables
public Visibility LblInvisibleTextVisibility
{
get => _lblInvisibleTextVisibility;
set
{
OnPropertyChanged(nameof(LblInvisibleTextVisibility)); //we are notifying the view that this property has been set, therefore the visibility has changed
_lblInvisibleTextVisibility = value;
}
}
private Visibility _lblInvisibleTextVisibility; //sub property used for holding values within the above monitored variable
#endregion
{
public class ViewModelExample : INotifyPropertyChanged
{
#region Variables
public Visibility LblInvisibleTextVisibility
{
get => _lblInvisibleTextVisibility;
set
{
OnPropertyChanged(nameof(LblInvisibleTextVisibility)); //we are notifying the view that this property has been set, therefore the visibility has changed
_lblInvisibleTextVisibility = value;
}
}
private Visibility _lblInvisibleTextVisibility; //sub property used for holding values within the above monitored variable
#endregion
#region Constructor
public ViewModelExample()
{
LblInvisibleTextVisibility = Visibility.Hidden; //reset initial value
BtnToggleLblVisibilityDelegateCommand = new DelegateCommand(ToggleVisibility); //implement this method now, this delegate command will call this when started!
}
#endregion
#region Delegate Commands
public DelegateCommand BtnToggleLblVisibilityDelegateCommand { get; set; } //set the function this button push will call in constructor above even
#endregion
public DelegateCommand BtnToggleLblVisibilityDelegateCommand { get; set; } //set the function this button push will call in constructor above even
#endregion
#region Delegate Command Functions
private void ToggleVisibility() //no parameters are allowed for this type of delegate command, however it is possible, i will attempt to do another vid on it
{
if(LblInvisibleTextVisibility == Visibility.Visible) //if visible
{
LblInvisibleTextVisibility = Visibility.Hidden; //hide it
}
else //if hidden
{
LblInvisibleTextVisibility = Visibility.Visible; //show it
}
private void ToggleVisibility() //no parameters are allowed for this type of delegate command, however it is possible, i will attempt to do another vid on it
{
if(LblInvisibleTextVisibility == Visibility.Visible) //if visible
{
LblInvisibleTextVisibility = Visibility.Hidden; //hide it
}
else //if hidden
{
LblInvisibleTextVisibility = Visibility.Visible; //show it
}
//with any luck, the button is bound to the 'BtnToggleLblVisibilityDelegateCommand' command, and the command calls THIS function, thus updateing the visibility of the label
//LETS SEEEE
}
//LETS SEEEE
}
#endregion
#region Property Changed Logic -- autogenerated
/* Property changed logic -- notifies the view when something has changed that we are watching, for example the LblInvisibleTextVisibility property */
public event PropertyChangedEventHandler PropertyChanged;
/* Property changed logic -- notifies the view when something has changed that we are watching, for example the LblInvisibleTextVisibility property */
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
Thank you for the Hard work and sharing your knowledge! my friend.
ReplyDeleteI have one question, will it load the data back to the form?
Is this possible to implement the same method for toggle buttons also?
ReplyDelete