Page 1 of 1

WPF Build An Application

#1 andrewsw  Icon User is online

  • Fire giant boob nipple gun!
  • member icon

Reputation: 3490
  • View blog
  • Posts: 11,904
  • Joined: 12-December 12

Posted 12 May 2014 - 04:28 PM

This tutorial follows-on from my WPF Build a Window tutorial-sequence:

Part 1 Part 2 Part 3 Part 4

It is not necessary for you to have completed the sequence, but I assume a lot of information covered, or introduced, in it.

This is a first draft towards creating a complete WPF Application.

The application built works but doesn't include the features, and good practices, that I am aiming towards. However, it does provide a basic, and working, framework that can be added-to. Oddly, I will start by listing what is not included:

Not Yet Included:

  • Code is not fully separated. In particular, there is some code-behind.
  • It doesn't fully follow the MVVM pattern, although I'm using 'Models' and 'Views' namespaces to help work towards this goal.
  • Doesn't use Commands (or ICommand).
  • Data Validation.
  • ControlTemplates or DataTemplates, and related features.
  • Data Conversion.

I had intended to include a number of these features from the outset (particularly Commands) but decided to prioritise getting an application up-and-running. It would simply be too frustrating (for you and for me) to type a mountain of code before being able to, eventually, run the application.

There is some code-behind (in ToDo.xaml.cs) that responds to Button clicks. This should be moved to a Command, or Commanding, set-up, preferably one that fits in with the MVVM pattern. Commanding Overview :MSDN. (This is a sacrifice I made in order to get the Application up-and-running at an early stage.)




Useful Links:

Data Binding Overview :MSDN
Basic WPF Databinding :DIC
How to: Create and Bind to an ObservableCollection :MSDN

About Commands, RelayCommands and/or MVVM:

Exploring a Model-View-ViewModel Application
Implementation of the ICommand interface for reuse
WPF Apps With The Model-View-ViewModel Design Pattern

Extending the ObservableCollection to add navigation methods




Start a new WPF Application named ToDoApplication.

Attached Image

I have carried across Styles, the Grid Structure, and a few other features from the earlier tutorial but, essentially, I first reduced it to a bare-bones structure. For example, I wanted to keep the Menu, ToolBar and StatusBar, but I've reduced them to just simple place-holder text.

You don't need any code from the earlier tutorial as all the necessary code is included here.

Here is the final folder-structure for reference:

Attached Image

Open the Solution Explorer, right-click the ToDoApplication Project and choose Add then New Folder. Create two folders named Models and Views.

Open App.xaml and change (or copy to) the following:

Spoiler

This is essentially the final set of Styles from the earlier tutorial, although I changed a couple of Menu/MenuItem settings.

You can close this file as we won't need it for the rest of the tutorial.

Change Mainwindow.xaml to this:
<Window x:Class="ToDoApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:ToDoApplication" 
        xmlns:views="clr-namespace:ToDoApplication.Views"
        Title="ToDo Application" SizeToContent="WidthAndHeight" 
        Style="{StaticResource styWindow}">
    <window.Resources>
    </window.Resources>
    <DockPanel>
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="_FILE">
                <MenuItem Header="Place Holder" />
            </MenuItem>
            <MenuItem Header="_EDIT">
                <MenuItem Header="Place Holder" />
            </MenuItem>
        </Menu>
        <ToolBarTray DockPanel.Dock="Top">
            <ToolBar>
                <Button Content="Place Holder" />
            </ToolBar>
        </ToolBarTray>
        <views:ToDo x:Name="vwToDo" DockPanel.Dock="Top" />
        <StatusBar DockPanel.Dock="Bottom">
            <TextBlock>Place Holder</TextBlock>
        </StatusBar>
    </DockPanel>
</Window>


The most significant difference from the earlier version is the complete removal of the Grid. This will be created as a UserControl and is then inserted into the main Window at this line:
        <views:ToDo x:Name="vwToDo" DockPanel.Dock="Top" />


Notice that views (and local) are added as namespace-references near the top of this file.

We will still need to explicitly define the namespace ToDoApplication.Views but it will correspond to the 'Views' folder within our application.

Now we will create our ToDo Class. Right-click the Models folder and choose Add, then Class..

ToDo.cs
using System;
using System.ComponentModel;

namespace ToDoApplication.Models {

    public class ToDo : INotifyPropertyChanged {
        public event PropertyChangedEventHandler PropertyChanged;

        private string _title;
        private DateTime? _startDate;
        private DateTime? _dueDate;
        private DateTime? _completedDate;
        private bool _completed;
        private Priorities _priority;

        public ToDo() {
        }
        public ToDo(string title) {
            this.Title = title;
        }

        public string Title {
            get { return _title; }
            set {
                if (_title == value) return;
                _title = value;
                RaisePropertyChanged("Title");
            }
        }
        public DateTime? StartDate {
            get { return _startDate; }
            set {
                if (_startDate == value) return;
                _startDate = value;
                RaisePropertyChanged("StartDate");
            }
        }
        public DateTime? DueDate {
            get { return _dueDate; }
            set {
                if (_dueDate == value) return;
                _dueDate = value;
                RaisePropertyChanged("DueDate");
            }
        }
        public DateTime? CompletedDate {
            get { return _completedDate; }
            set {
                if (_completedDate == value) return;
                _completedDate = value;
                RaisePropertyChanged("CompletedDate");
            }
        }
        public bool Completed {
            get { return _completed; }
            set {
                if (_completed == value) return;
                _completed = value;
                RaisePropertyChanged("Completed");
            }
        }
        public Priorities Priority {
            get { return _priority; }
            set {
                if (_priority == value) return;
                _priority = value;
                RaisePropertyChanged("Priority");
            }
        }

        private void RaisePropertyChanged(string propertyName) {
            if (PropertyChanged != null) {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    public enum Priorities {
        Medium, High, Low
    }
}


Notice that this is within the namespace ToDoApplication.Models.

We are using a simple enumeration for Priority; it is defined at the bottom, but still within the namespace.

Right-click the Models folder again and create the class MyObservableCollection:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace ToDoApplication.Models {
    public class MyObservableCollection<T> : ObservableCollection<T> {
        // http://elegantcode.com/2009/09/25/extending-the-observablecollection-to-add-navigation-methods-such-as-movefirst-movenext-movelast-and-moveprevious/

        public MyObservableCollection() : base() {
        }
        public MyObservableCollection(List<T> list) : base(list) {
        }
        public MyObservableCollection(IEnumerable<T> collection) : base(collection) {
        }

        private System.ComponentModel.ICollectionView GetDefaultView() {
            return System.Windows.Data.CollectionViewSource.GetDefaultView(this);
        }

        public int CurrentPosition {
            get {
                return this.GetDefaultView().CurrentPosition;
            }
        }

        public void MoveFirst() {
            this.GetDefaultView().MoveCurrentToFirst();
        }
        public void MovePrevious() {
            this.GetDefaultView().MoveCurrentToPrevious();
        }
        public void MoveNext() {
            this.GetDefaultView().MoveCurrentToNext();
        }
        public void MoveLast() {
            this.GetDefaultView().MoveCurrentToLast();
        }

        public bool CanMoveBack() {
            return this.CurrentPosition > 0;
        }
        public bool CanMoveForward() {
            return (this.Count > 0) && (this.CurrentPosition < this.Count - 1);
        }
    }
}


I have adapted this from this tutorial.

I like this idea: creating our own, re-usable/generic, collection class. Unfortunately, I think the tutorial is incomplete, as it doesn't give enough information about how to connect this to a Commanding structure. Rather than pursue this at this stage, perhaps over-complicating the tutorial, I decided just to use a little code-behind. (Besides, there is more than one way to set-up a Commanding structure.)




An ObservableCollection has a DefaultView. It is possible to create, and manipulate, more than one View of this collection.

Changing the View's CurrentPosition will change which item (which ToDo item) we are looking at in the window.

As you can see, the DefaultView already has methods of MoveCurrentToFirst(), MoveCurrentToNext(), etc., so we are essentially building a helper-class.

We can now sub-class our collection-class. Right-click Models again and create ToDoList.cs:
using System;

namespace ToDoApplication.Models {
    public class ToDoList : MyObservableCollection<ToDo> {
        public ToDoList()
            : base() {
            this.Add(new ToDo("Your Title Here"));
        }
        public ToDoList(string title)
            : base() {
            this.Add(new ToDo(title));
        }
    }
}


You might chose to remove "Your Title Here" as it is annoying to have to replace this text for the first ToDo item. (I included this at an early stage, to be able to show that my model was working.)

I investigated setting up place-holder text instead, which would disappear when clicking into the TextBox. This is not as straight-forward as it could be: DIC discussion.

[I also attempted using Focus() and SelectAll() so that, at least, typing would replace this default text. It didn't work for me straight-off and I decided it wasn't important for this tutorial.]

Now let's create the Grid. Right-click the Views folder and create a UserControl named ToDo.xaml:
<UserControl x:Class="ToDoApplication.Views.ToDo"
             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:models="clr-namespace:ToDoApplication.Models" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition Height="50" />
            <RowDefinition Height="50" />
            <RowDefinition Height="50" />
            <RowDefinition Height="50" />
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition  Width="100" />
            <ColumnDefinition  Width="100" />
            <ColumnDefinition  Width="100" />
            <ColumnDefinition  Width="100" />
            <ColumnDefinition  Width="100" />
        </Grid.ColumnDefinitions>
        <Label Grid.Row="0" Grid.Column="0" Content="Title" />
        <TextBox Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="3" x:Name="txtTitle" >
            <Binding Path="/Title" Mode="TwoWay" />
        </TextBox>
        <Label Grid.Row="1" Grid.Column="0" Content="Start Date" />
        <DatePicker Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" x:Name="dpStartDate">
            <DatePicker.SelectedDate>
                <Binding Path="/StartDate" Mode="TwoWay" />
            </DatePicker.SelectedDate>
        </DatePicker>
        <Label Grid.Row="2" Grid.Column="0" Content="Due Date" />
        <DatePicker Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" x:Name="dpDueDate">
            <DatePicker.SelectedDate>
                <Binding Path="/DueDate" Mode="TwoWay" />
            </DatePicker.SelectedDate>
        </DatePicker>
        <Label Grid.Row="3" Grid.Column="0" Content="Completed" />
        <DatePicker Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" x:Name="dpCompletedDate">
            <DatePicker.SelectedDate>
                <Binding Path="/CompletedDate" Mode="TwoWay" />
            </DatePicker.SelectedDate>
        </DatePicker>
        <CheckBox Grid.Row="3" Grid.Column="3" Grid.ColumnSpan="2" x:Name="ckbCompleted" 
                      IsChecked="{Binding Path=/Completed, Mode=TwoWay}">
            Completed?
        </CheckBox>
        <Label Grid.Row="4" Grid.Column="0" Content="Priority" />
        <ComboBox Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2" x:Name="cboPriority" 
                  SelectedValue="{Binding Path=/Priority, Mode=TwoWay}">
            <models:Priorities>Medium</models:Priorities>
            <models:Priorities>High</models:Priorities>
            <models:Priorities>Low</models:Priorities>
        </ComboBox>
        <StackPanel Grid.Row="5" 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>
    </Grid>
</UserControl>


At the top we've added a namespace-reference for Models. (This is only needed because of the way we specify the enumeration-values.)

This Grid is already inserted (by reference) in the main window. In Mainwindow.xaml.cs we will set the Grid's DataContext to a new ToDoList().

Once the DataContext is set the slash "/" will refer to the current item of the view. (That is, of our ObservableCollection's DefaultView.) Then Path="/Title" binds to the Title property of the current ToDo item.
        <models:Priorities>Medium</models:Priorities>
        <models:Priorities>High</models:Priorities>
        <models:Priorities>Low</models:Priorities>


This repeats the enumerated values. This repetition shouldn't be necessary, although it may require switching from an enum to a collection (or maybe a Class). An enum will suffice for the purpose of this tutorial.

Create the code behind our view, in ToDo.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 ToDoApplication.Models;

namespace ToDoApplication.Views {
    /// <summary>
    /// Interaction logic for ToDo.xaml
    /// </summary>
    public partial class ToDo : UserControl {
        private ToDoList _todoList;

        public ToDo() {
            InitializeComponent();
        }

        private void CommonNavHandler(object sender, RoutedEventArgs e) {
            FrameworkElement feSource = e.Source as FrameworkElement;
            if (_todoList == null) {
                _todoList = (ToDoList)this.DataContext;
            }
            switch (feSource.Name) {
                case "btnFirst":
                    if (this._todoList.CanMoveBack())
                        this._todoList.MoveFirst();
                    break;
                case "btnPrevious":
                    if (this._todoList.CanMoveBack())
                        this._todoList.MovePrevious();
                    break;
                case "btnNext":
                    if (this._todoList.CanMoveForward())
                        this._todoList.MoveNext();
                    break;
                case "btnLast":
                    if (this._todoList.CanMoveForward())
                        this._todoList.MoveLast();
                    break;
                case "btnNew":
                    this._todoList.Add(new ToDoApplication.Models.ToDo());
                    if (this._todoList.CanMoveForward())
                        this._todoList.MoveLast();
                    break;
                default:
                    break;
            }
        }
    }
}


There isn't too much of this code but, ideally, we want to move it out into a Commanding structure.

This common navigation-handler (CommonNavHandler) was discussed in part 3 of my 'Build a Window' tutorial.
        if (_todoList == null) {
            _todoList = (ToDoList)this.DataContext;
        }


The DataContext is assigned in the main Window's code, so isn't immediately available to assign to _todoList in this view. I would prefer to make this assignment once and get rid of the if-statement. This should become possible as we pursue the MVVM pattern. [This could be achieved by using code to not just set the DataContext but to also instantiate and insert the Grid into the window.]

Finally, we need to modify Mainwindow.xaml.cs, simply to provide the DataContext for the Grid:
// I've omitted the default using statements here
using ToDoApplication.Models;

namespace ToDoApplication {
    /// <summary>
    /// Interaction logic for Mainwindow.xaml
    /// </summary>
    public partial class MainWindow : Window {

        public MainWindow() {
            InitializeComponent();
            Loaded += MainWindowLoaded;
        }

        private void MainWindowLoaded(object sender, RoutedEventArgs e) {
            vwToDo.DataContext = new ToDoList();
        }
    }
}





You can run the application and you will be able to add ToDo items, and navigate back and forwards through them. The list won't be persisted, although serializing and de-serializing the list could be considered.

As I stated at the outset, and I want to emphasize, I am not proposing this as a complete Application. It does, however, provide a framework to study and add other features - Commands, Data Validation (see the list at the beginning) - and to modify to follow the MVVM Pattern.

This post has been edited by andrewsw: 12 May 2014 - 05:50 PM


Is This A Good Question/Topic? 1
  • +

Replies To: WPF Build An Application

#2 sithius92  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 36
  • View blog
  • Posts: 164
  • Joined: 01-August 08

Posted 21 August 2014 - 08:51 AM

Are you going to do a tutorial about MVVM? Learning this, for me, was a major insight into WPF and separation of concerns. I had never used a pattern like MVC/MVP/MVVM before I started with WPF. I would be interested in contributing to a tutorial for MVVM if you planned on doing one.
Was This Post Helpful? 0
  • +
  • -

#3 andrewsw  Icon User is online

  • Fire giant boob nipple gun!
  • member icon

Reputation: 3490
  • View blog
  • Posts: 11,904
  • Joined: 12-December 12

Posted 21 August 2014 - 09:22 AM

I thought about it on and off ;).

I tried to bear MVVM in mind while writing the tutorial. To my understanding, my tutorial is not too far removed from this pattern. I think it would just require creating a couple of Views, and creating wrapper methods (for the Model) in View-Models for each of these Views.

(I couldn't quite decide how I wanted to substitute these Views into the current Window, and how to navigate between them. That is, whether to rebuild it as a NavigationWindow, or just add Buttons myself. Perhaps creating an ISwitchable interface like this guy.)

But, currently, no, I've put this idea on hold.

I would welcome such a tutorial myself if you're keen to give it a go! You are quite welcome to use anything from my tutorial - there is a zip file at the end of the last section - if you think the application, or any of its parts, would be useful to you.

This post has been edited by andrewsw: 21 August 2014 - 09:26 AM

Was This Post Helpful? 0
  • +
  • -

Page 1 of 1