Page 1 of 1

WPF Build a Window 3, Adding Some Code

#1 andrewsw  Icon User is online

  • Fire giant boob nipple gun!
  • member icon

Reputation: 3473
  • View blog
  • Posts: 11,786
  • Joined: 12-December 12

Posted 05 May 2014 - 03:32 PM

This continues directly from Part 2 and introduces the use of code (using C#). Part 1. Part 4.

Topic List:

  • Traditional Event Handling
  • Alternatives to Code-Behind
  • Routed Events
  • An EventTrigger (no code)
  • More on ApplicationCommands
  • Custom Commands

Traditional Event Handling

If you are used to WinForms the following will seem very familiar. Add a Close button to our Window, after the Expander:
            </Expander>
            <Button Grid.Row="4" Grid.Column="6" Content="Close" Name="btnClose"
                    Click="btnClose_Click" ToolTip="Close the form" />
        </Grid>


When you are within Click="|" VS offers 'New Event Handler'. You can name the event yourself but accept the default for now. Double-click 'New Event Handler' and it inserts the name 'btnClose_Click'. You can then right-click it and choose Navigate to Event Handler to be taken to the code behind, in the file Mainwindow.xaml.cs:
        private void btnClose_Click(object sender, RoutedEventArgs e) {

        }


(If you name the event-handler yourself you can still right-click it and choose Navigate to.. and it will create the initial code for you.)

Modify the code to this:
        private void btnClose_Click(object sender, RoutedEventArgs e) {
            Application.Current.Shutdown();
        }


If you want to check what events are available click in the word Button, or another control, view the Properties Window and click the lightning bolt to list the available Events.

Attached Image

From here you can also double-click an empty box to generate the event-handler code, or double-click an existing handler-name to go to that code.

Alternatives to Code-Behind

Firstly I will say that it is not wrong to use event-handling and code-behind. The history of WinForms illustrates the issues though. With WinForms it is the only way to achieve interactivity on a Form and it leads beginners in particular to mix the presentation-logic with application-logic. Once they acquire this habit it becomes very hard to break, particularly when it is so easy to start writing code-behind.

The problem with mixing presentation and application code/logic is that it is very difficult to unit test code. Moving to a different presentation-layer - perhaps from WinForms to WPF or to a website - requires a complete re-write. It also creates messy code, cf. separation of concerns:

wiki said:

The value of separation of concerns is simplifying development and maintenance of computer programs. When concerns are well separated, individual sections can be developed and updated independently. Of special value is the ability to later improve or modify one section of code without having to know the details of other sections, and without having to make corresponding changes to those sections.


With WPF we have a number of alternatives. Within the XAML we can use Triggers (Style, Data, Event, etc., Triggers). These keep effects, animations and property changes within the XAML itself (the presentation-layer) without requiring code.

MSDN said:

There are other types of triggers. MultiTrigger allows you to apply changes based on the state of multiple properties. EventTrigger allows you to apply changes when an event occurs. DataTrigger and MultiDataTrigger are for data-bound properties.

Trigger Class :MSDN

We can also create Custom Commands - see the later section.

Commanding Overview :MSDN

Significantly with WPF, we can bind controls and control-properties directly to objects or object-properties. This is exemplified in the Model-View-ViewModel pattern.

wiki said:

MVVM was designed to make use of data binding functions in WPF to better facilitate the separation of view layer development from the rest of the pattern by removing virtually all GUI code ("code-behind") from the view layer. Instead of requiring user interface (UX) developers to write GUI code, they can use the framework markup language (e.g., XAML) and create bindings to the view model, which is written and maintained by application developers. This separation of roles allows interactive designers to focus on UX needs rather than programming of business logic, allowing for the layers of an application to be developed in multiple work streams for higher productivity. Even when a single developer works on the entire code base a proper separation of the view from the model is more productive as the user interface typically changes frequently and late in the development cycle based on end-user feedback.


Conversely, people often take this information too much at face-value and wrongly assume that code-behind is a bad thing, and somehow breaks the MVVM pattern. It doesn't.

Summary:

There is nothing wrong with code-behind as long as it involves UI interactivity and doesn't contain any business-logic. Yes, it may be technically possible to write an entire application without any code-behind (in Mainwindow.xaml.cs) but it is not worth sacrificing understandable, and simple, code to achieve this aim.

At the end of the day, writing XAML-equivalent code can be quite complicated. If I can achieve what I want with a couple of lines of event-handler code then I will do this.

I apologise for any confusion, particularly at this early stage, but it is a necessary discussion.

Routed Events

WPF elements use RoutedEvents: direct, bubbling and tunneling.

  • Direct events are similar to WinForm events: only the source element itself can call event handlers.
  • Bubbling events are the most common in WPF. The event starts with the source element and 'bubbles' up to its parent, then its parent and so on.
  • Tunneling events start at the element root and travel downward to the source element.

Routed Events Overview :MSDN
Understanding Routed Events and Commands In WPF :MSDN Magazine

The following modification demonstrates the top-level scenario mentioned in the first of these two links:
        <StackPanel Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="5" Orientation="Horizontal" 
            HorizontalAlignment="Center" Button.Click="CommonNavHandler">
            <Button Content="First" Name="btnFirst" />
            <Button Content="Previous" Name="btnPrevious" />
            <Button Content="Next" Name="btnNext" />
            <Button Content="Last" Name="btnLast" />
            <Button Content="New" Name="btnNew" />
        </StackPanel>


The StackPanel declares the Button.Click event-handler. Clicking any Button within the StackPanel will execute the handler. (It it weren't handled it would bubble-up to the StackPanel and beyond.)

Create the event-handler in the way outlined in the first section above:
        private void CommonNavHandler(object sender, RoutedEventArgs e) {
            FrameworkElement feSource = e.Source as FrameworkElement;
            switch (feSource.Name) {
                case "btnFirst":
                    MessageBox.Show("You clicked 'First'.");
                    break;
                case "btnPrevious":
                    MessageBox.Show("You clicked 'Previous'.");
                    break;
                case "btnNext":
                     MessageBox.Show("You clicked 'etc..'.");
                    break;
                case "btnLast":
                    break;
                case "btnNew":
                    break;
                default:
                    break;
            }
        }


Note: This demonstates the principle but may not be appropriate for these types of buttons.

An EventTrigger (no code)

EventTriggers listen for events such as Click and are typically used to initiate animations. Modify App.xaml to include this simple, annoying, example:
        <Style TargetType="Button" BasedOn="{StaticResource controlStyle}">
            <Setter Property="HorizontalContentAlignment" Value="Center" />
            <Setter Property="MinWidth" Value="70" />
            <Setter Property="Opacity" Value="1" />
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="FontWeight" Value="Bold" />
                </Trigger>
                <EventTrigger RoutedEvent="Click">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="Opacity"
                           From="0" To="1" Duration="0:0:2" />
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Style.Triggers>
        </Style>


When any Button is clicked it immediately disappears (Opacity = 0) and then reappears over a two-second period. Recall that we may need to set the initial Opacity to 1 within the Style-definition, as it may be over-ruled by an inline attribute - and we wouldn't see the animation. Rather than worry about whether it will, or won't, be over-ruled I always redefine any property that will be changed, or animated, within the Style-defintion.

FULL CODE for App.xaml for reference:

Spoiler

More on ApplicationCommands

I discussed ApplicationCommands in Part2, mentioning that for most of them it requires us to provide the context, and meaning, of the Command.

To demonstrate this start a new WPF application and add a Button to the Window:
<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">
    <window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Close" Executed="OnClose" />
    </window.CommandBindings>
    <Grid>
        <Button Content="Close Me" Command="ApplicationCommands.Close" />
    </Grid>
</Window>


When the Close command is executed the OnClose() method is invoked. We still need to write the code-behind though:
        private void OnClose(object sender, ExecutedRoutedEventArgs e) {
            Application.Current.Shutdown();
        }


We can also use <window.CommandBindings> to associate our Custom Commands (see the next section) with method-calls. However, because we will need to create code and code-behind anyway, I don't really see an advantage in doing this part in the XAML.

Custom Commands

Custom Commands can be grouped together in a static class.

Full disclosure: I've borrowed the following code from the book C# 4.0 How To by Ben Watson. This is a great little book and has some really interesting code, but it isn't a book for beginners, nor a tutorial.

Return to our main application and add the following static class (MyWpfCommands.cs):
using System;
using System.Windows.Input;     // RoutedUICommand etc.

namespace ProgrammersWPF2 {
    public class MyWpfCommands {
        public static RoutedUICommand ExitCommand;

        static MyWpfCommands() {
            InputGestureCollection exitInputs = new InputGestureCollection();
            exitInputs.Add(new KeyGesture(Key.F4, ModifierKeys.Alt));

            ExitCommand = new RoutedUICommand("Exit Application", "ExitApplication",
                typeof(MyWpfCommands), exitInputs);
        }
    }
}


This creates our new ExitCommand and assigns it the keyboard-shortcut of Alt-F4. We can essentially copy and paste these few code-lines to add any number of commands, keeping their definition in this single static-class.

Modify the code-behind (Mainwindow.xaml.cs) to add this code:
namespace ProgrammersWPF2 {
    /// <summary>
    /// Interaction logic for Mainwindow.xaml
    /// </summary>
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();

            // setup handlers for our custom commands
            CommandBinding cmdBindingExit = new CommandBinding(MyWpfCommands.ExitCommand);
            cmdBindingExit.Executed += new ExecutedRoutedEventHandler(cmdBindingExit_Executed);

            this.CommandBindings.Add(cmdBindingExit);
        }

        private void cmdBindingExit_Executed(object sender, ExecutedRoutedEventArgs e) {
            Application.Current.Shutdown();
        }


This creates a CommandBinding, and attaches it to our application's CommandBindings, which was achieved in XAML in the previous section.

The key-point is that the code within cmdBindingExit_Executed will be executed when we call our Custom Command, named ExitCommand.

Now we need to hook-up our Command in the XAML. There is a complication though. Although our new class and our application exist within the same namespace (ProgrammersWPF2) this doesn't carry through to the XAML and so we need to create a namespace-reference at the top of Mainwindow.xaml:
<Window x:Class="ProgrammersWPF2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:ProgrammersWPF2"


We are essentially mapping an XML namespace to an assembly. If our class were in a different solution we would also need to add a reference to it in our project.

Now we can use local: to locate our Commands. Modify our Menu item so that it uses our Command:
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="_File">
                <MenuItem Header="_Exit" Command="local:MyWpfCommands.ExitCommand" />
            </MenuItem>


It should work now! Choosing File/Exit, or pressing Alt-F4, should exit the application.

Modify the Close-Button we added earlier to also use this Command:
        <Button Grid.Row="4" Grid.Column="6" Content="Close" Name="btnClose"
                Command="local:MyWpfCommands.ExitCommand" ToolTip="Close the form" />


Once you have both exit-methods working we can add something further: the CanExecute event-handler. This is a significant feature in that it won't matter where the Command is called from - if CanExecute returns false then the Command won't be executed. In Mainwindow.xaml.cs:
        public MainWindow() {
            InitializeComponent();

            // setup handlers for our custom commands
            CommandBinding cmdBindingExit = new CommandBinding(MyWpfCommands.ExitCommand);
            cmdBindingExit.Executed += new ExecutedRoutedEventHandler(cmdBindingExit_Executed);

            cmdBindingExit.CanExecute += new CanExecuteRoutedEventHandler(cmdBindingExit_CanExecute);

            this.CommandBindings.Add(cmdBindingExit);
        }

        private void cmdBindingExit_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = (txtFirstName.Text.Length == 0);
        }

        private void cmdBindingExit_Executed(object sender, ExecutedRoutedEventArgs e) {
            Application.Current.Shutdown();
        }


To demonstrate I am just tying CanExecute to the characters in the firstname TextBox. If there are any characters then the application won't be shutdown. (It can still be shutdown using the close-icon.)

Run the application and type some characters in the firstname TextBox. Notice that both the Close Button and Exit menu-option are disabled.




In the code-behind, and the CanExecute and Executed methods, we can refer to other controls on the window. However, it is important that we follow the intended purpose of the Command. The ExitCommand should either exit, or not exit, the application.

Rather than using a static class the ICommand interface can be implemented. Curtis Rutland's tutorial Basic WPF Databinding uses this approach.

Commands are an interesting, but not essential, option. They can be part of the MVVM pattern but are not a requirement for it.

Commands in MVVM :codeproject




FULL CODE for Mainwindow.xaml:

Spoiler

This post has been edited by andrewsw: 11 May 2014 - 08:15 AM


Is This A Good Question/Topic? 0
  • +

Page 1 of 1