Page 1 of 1

WPF Build a Window 2, more XAML

#1 andrewsw  Icon User is offline

  • It's just been revoked!
  • member icon

Reputation: 3808
  • View blog
  • Posts: 13,508
  • Joined: 12-December 12

Posted 05 May 2014 - 02:10 PM

I consider this a continuation of this first tutorial:

WPF Build A Window, With Style
Part 3

However, the first part is not essential (but recommended) and I include a full copy of the necessary code from that part.

In order to continue the tutorial I need to adopt a quite personal approach. WPF is quite big and I want to break it down into manageable chunks. To this end, some subjects are only introduced briefly. (I do provide some useful links for you to pursue.)

I also have a specific objective: I want to continue concentrating on XAML-only features. That is, to try as far as possible to distinguish between what XAML alone is capable of, and what requires coding.

Topic List

  • Menu and ApplicationCommands
  • Toolbar, Image-Resources and Simple Binding
  • StatusBar and Simple Property-Binding
  • Brief Intro: Canvas, Ellipse, Path
  • Inline Styles, DynamicResources and DataTriggers
  • Expander and Transforms




WPF tutorials like to show off the capabilities of WPF: Arbitrary nesting of controls, 3D effects, animations, and more. I won't be distracted by these topics. These are large topics which can be deferred.

I am still not attempting to build an Application. The completed Window still doesn't do a great deal, apart from introducing features.

General Hint: Keep an open mind with WPF. In particular, we will often have a choice to do something either in XAML or code (or a combination of the two) and the decision is not clear-cut. (This is particularly disorientating when coming from WinForms, where the choices are fewer and more obvious.)




Up and Running

Start a new application which I called, imaginatively, ProgrammersWPF2. You can copy the two files below to get up-and-running, which include the following slight changes from the original application:

Slight Changes:
  • Margin setting moved into Control-Style
  • Styles moved into Application Resources
  • Some Style Triggers removed, particularly the glow-effect
  • An extra column is added to the Grid.

These changes are not essential (apart from the new column) and you can, if you prefer, continue with the previous version of the application.

App.xaml
Spoiler

Mainwindow.xaml
Spoiler

If you run the application it should look like this:

Attached Image

The end result will look like this:

Attached Image

Why is there a smiley-face? I wanted to introduce Shapes. (The close button is added in part 3.)

Menu and ApplicationCommands

We will add a Menu and MenuItems to our window.
    <DockPanel>
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="_File">
                <MenuItem Header="_Exit" />
            </MenuItem>
            <MenuItem Header="_Edit">
                <MenuItem Header="C_ut" Command="ApplicationCommands.Cut" />
                <MenuItem Header="_Copy" Command="ApplicationCommands.Copy" />
                <MenuItem Header="_Paste" Command="Paste" />
                <!-- 'ApplicationCommands' will be searched -->
                <MenuItem Header="_Toggle Me" IsCheckable="True" Name="menuItemToggle" />
            </MenuItem>
        </Menu>


Our DockPanel only contained a Grid. Now that it will contain other controls we need to use the DockPanel.Dock attribute. Add DockPanel.Dock="Top" to the Grid as well, so that it will sit beneath the Menu.

Note: I used a StackPanel for the navigation buttons (Next, Previous) but we can generally choose to use either a DockPanel or StackPanel. Choose Between StackPanel and DockPanel :MSDN.

In WinForms the ampersand (&) was used to identify an accelerator key. That is, pressing the single-letter that followed the ampersand would choose that option if the menu was active. If the menu is not active then pressing Alt-and the letter chooses that menu option. The ampersand has a different meaning in XAML/XML so WPF uses an underscore.

However, it doesn't behave well in my opinion. (At least, not by default.) The letter F in File is not underlined so it is not apparent that we can press Alt-F to activate this menu. (We have to press Alt before we see this.) Similarly, the letters of Cut, Copy and Paste are not underlined unless we use Alt-F to activate the menu and use the right-arrow to move to the Edit menu. This is by design and if we want the underline to always appear we need to go to Window's Ease of Access Center and choose "underline keyboard shortcuts and access keys".

The Menu doesn't inherit the font and font-size that we set in the Window-Style, in App.xaml, so I added the following settings in the Mainwindow.xaml's window.Resources:
    <window.Resources>
        <Style TargetType="Menu">
            <Setter Property="FontFamily" Value="Verdana" />
            <Setter Property="FontSize" Value="12" />
        </Style>
        <Style TargetType="MenuItem">
            <Setter Property="VerticalContentAlignment" Value="Center" />
            <Setter Property="MinHeight" Value="26" />
        </Style>
    </window.Resources>


Homework: Move these into the Application.Resources, they make more sense as application-wide settings.

Rather than the more traditional event-handlers of WinForms we have the opportunity to create or use Commands:
        <MenuItem Header="C_ut" Command="ApplicationCommands.Cut" />
        <MenuItem Header="_Copy" Command="ApplicationCommands.Copy" />
        <MenuItem Header="_Paste" Command="Paste" />
        <!-- 'ApplicationCommands' will be searched -->


There is an in-built list of ApplicationCommands that we can use, such as Cut, Copy and Paste. These don't require any code. As long as we have some text selected, typically in a TextBox, then Cut and Copy are available and work as expected.

This looks a promising innovation. However, the example in the linked page only demonstrates Paste. If you read through some of the other commands you'll discover that a lot of them don't have a default implementation. It requires us to provide a meaning and context for, for example, New and Print.

We can create Custom Commands, which I do in Part 3.

Commanding Overview :MSDN

Toolbar, Image-Resources and Simple Binding

We will add a Toolbar, which sits in a ToolBarTray. Here is the code but you should obtain images beforehand, discussed below:
        </Menu>
        <ToolBarTray DockPanel.Dock="Top">
            <ToolBar>
                <Button Command="ApplicationCommands.Cut">
                    <Image Source="Resources\Cut.png" />
                </Button>
                <Button Command="ApplicationCommands.Copy">
                    <Image Source="Resources\Copy.png" />
                </Button>
                <Button Command="ApplicationCommands.Paste">
                    <Image Source="Resources\Paste.png" />
                </Button>
                <CheckBox IsChecked="{Binding Mode=TwoWay, ElementName=menuItemToggle, Path=IsChecked}" 
                          Content="Toggled?" />
            </ToolBar>
        </ToolBarTray>


This is Docked at the Top (of the DockPanel) but will sit underneath the Menu because it follows it in the document order.

As with WinForms, we can add images as Resources from the Project menu, ProjectName Properties.., Resources tab. We can drop images onto this surface.

Visual Studio 2010 (and 2008, 2005) already contains the Visual Studio Image Library.

MSDN said:

The Visual Studio Image Library is installed on your computer together with Visual Studio. To access the files in the image library, you must extract them from VS2010ImageLibrary.zip. This file is typically installed in ..\Program Files\Microsoft Visual Studio 10.0\Common7\VS2010ImageLibrary\1033\.

If you don't have this you can download it, once you've read and agreed the License Terms.

I used the 16x16 .png versions of these icons, but renamed them to Cut.png, etc.. You can, of course, use whatever images you want.

The images are shown in the Designer, but disappear when running the application. Find the images in Solution Explorer, right-click one of the them and choose Properties. Change Build Action to Resource. You might also change the property Copy to Output Directory to Copy Always to make the images deployable. Do the same for the other two images. Hint: If you use the Shift-key you can do this for all three images in one go.
        <CheckBox IsChecked="{Binding Mode=TwoWay, ElementName=menuItemToggle, Path=IsChecked}" 
                  Content="Toggled?" />


In a ToolBar a CheckBox behaves as a toggle-button. In which case, you may wish to add an image as a child-control to the CheckBox, to make it clearer when it is pressed (checked). To get a more familiar check-mark you could embed the CheckBox in a Button:
    <Button>
        <CheckBox IsChecked="{Binding Mode=TwoWay, ElementName=menuItemToggle, Path=IsChecked}" 
                  Content="Toggled?" />
    </Button>


If you do this though, you might find, as I did, that the Button is initially too tall and distorts the toolbar. This could be an opportunity for you to explore Button (and/or Toolbar) attributes or Styles.

In the menu I named the 'Toggle Me' MenuItem menuItemToggle. This name is then used with the CheckBox to create a simple, TwoWay, property binding. Run the application. The menu-item and toolbar-checkbox are both either checked, or not checked.

Binding Class :MSDN

MSDN said:

Windows Presentation Foundation (WPF) data binding provides a simple and consistent way for applications to present and interact with data. Data binding enables you to synchronize the values of the properties of two different objects.

This is, as you might guess, a very big subject and I won't pursue it here.

StatusBar and Simple Property-Binding

We will add a StatusBar which displays the fullname of the programmer. This is added after the Grid and docked to the bottom (of the DockPanel).
        </Grid>
        <StatusBar DockPanel.Dock="Bottom">
            <TextBlock>Full Name:
                <TextBlock Text="{Binding ElementName=txtFirstName, Path=Text}" />
                <TextBlock Text="{Binding ElementName=txtLastName, Path=Text}" />
            </TextBlock>
        </StatusBar>


Binding Class :MSDN

Again, I won't discuss Binding in any detail at this stage. However, this example is straight-forward. The Text of the TextBlocks is bound to the Text-property of specific elements, using their ElementNames to identify them. No code required!

If we were building a full application, with a data-source, then the fullname would be bound to properties of a data-bound object, not directly to Controls of the window. (A Control is a property of a Window but this does not provide the code separation that we seek.)

Brief Intro: Canvas, Ellipse, Path

To introduce Shapes and Drawing we'll add (for no particular reason!) a smiley-face after the StackPanel. (Because we are using a Grid the ordering of the elements isn't always significant, although it can be.)

Shapes and Basic Drawing in WPF Overview :MSDN
    </StackPanel>
        <Canvas Grid.Row="0" Grid.Column="5" Grid.RowSpan="2" Grid.ColumnSpan="2">
            <Ellipse Canvas.Left="60" Canvas.Top="10" Width="80" Height="80" 
                     Stroke="Blue" StrokeThickness="4" Fill="Yellow" />
            <Ellipse Canvas.Left="75" Canvas.Top="25" Width="20" Height="20" 
                     Stroke="Blue" StrokeThickness="3" Fill="White" />
            <Ellipse Canvas.Left="80" Canvas.Top="35" Width="6" Height="5" Fill="Black" />
            <Ellipse Canvas.Left="105" Canvas.Top="25" Width="20" Height="20" 
                     Stroke="Blue" StrokeThickness="3" Fill="White" />
            <Ellipse Canvas.Left="110" Canvas.Top="35" Width="6" Height="5" Fill="Black" />
            <Path x:Name="smile" Stroke="Blue" StrokeThickness="4" Data="M 80,64 Q 100,90 120,64" />
        </Canvas>


A Canvas is a Layout control and can be used to precisely (absolutely) position controls. Although this is possible, a Canvas is more often employed as a drawing-surface.

It should be fairly obvious what is going on. If you want you could add the Ellipses one at a time to see the face being drawn in the Designer. Experiment with Stroke-colour, Widths and Heights, etc..

Using Paths and Geometries :MSDN
(this is in the same page just linked)

MSDN said:

The Path class enables you to draw curves and complex shapes. These curves and shapes are described using Geometry objects. To use a Path, you create a Geometry and use it to set the Path object's Data property.

Data="M 80,64 Q 100,90 120,64" The letter M (for 'move to') establishes a starting-point for the Path. Q specifies a control-point and end-point for a quadratic Bezier curve. Experiment with these values. Start by changing "100, 90" to bend the curve in different directions.

Inline Styles, DynamicResources and DataTriggers

Replace the Path definition with the following:
        <Path x:Name="smile" Stroke="Blue" StrokeThickness="4">
            <Path.Style>
                <Style TargetType="Path">
                    <Setter Property="Data" Value="M 80,64 Q 100,90 120,64" />
                    <Style.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Data" Value="M 80,64 Q 100,50 120,64" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </Path.Style>
        </Path>


We are providing an inline style.

MSDN said:

In Extensible Application Markup Language (XAML), style and template properties can technically be set in one of two ways. You can use attribute syntax to reference a style that was defined within a resource, for example <object Style="{StaticResource myResourceKey}" .../>. Or you can use property element syntax to define a style inline, for instance:

< object >
< object .Style>
< Style .../>
</ object .Style>
</ object >

This is less flexible than providing the Style as a (Static or Dynamic) Resource as it is scoped to the containing element and cannot be re-used as it has no resource-key.

If you run this then you will see the smile turn to a frown (an upside-down smile..). However, it doesn't work properly and alternates rapidly between the two expressions. This is because it is triggered by having the mouse over the Path, which then moves the Path away from the mouse (by changing its Data), triggering the smile, etc., etc..

Note: We had to move the Data-definition "M 80,64 Q 100,90 120,64" into the Style itself because, if we left it as an attribute of the Path, the attribute has a higher priority than the Style. Try moving this out of the Style and back as an attribute of the Path; the smile will no longer change and there is no warning from VS. (I was caught out by this myself, and it took a while to realise why nothing was happening.)

What we need to do is to attach the IsMouseOver trigger to the Canvas itself. Change to this:
        <Path x:Name="smile" Stroke="Blue" StrokeThickness="4">
            <Path.Resources>
                <Geometry x:Key="geoFrown">
                    M 80,64 Q 100,50 120,64
                </Geometry>
            </Path.Resources>
            <Path.Style>
                <Style TargetType="Path">
                    <Setter Property="Data" Value="M 80,64 Q 100,90 120,64" />
                    <Style.Triggers>
                        <!--<Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Data" Value="M 80,64 Q 100,50 120,64" />
                        </Trigger>-->
                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, 
                            AncestorType={x:Type Canvas}}, Path=IsMouseOver}" Value="True">
                            <Setter Property="Data" Value="{DynamicResource geoFrown}" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Path.Style>


I have taken the opportunity to introduce a DynamicResource, the Geometry named 'geoFrown'. It is Dynamic because, essentially, it appears inline with the document content, unlike the StaticResources of window.Resources and Application.Resources that appear at the top of the Window and in a separate file (App.xaml).

The distinction between Dynamic and Static Resources is more involved than this but, for our currrent purpose, it is sufficient to say that any resource appearing inline is Dynamic, and at the top of the file or elsewhere is Static.

DataTrigger Class :MSDN

I've struggled to find a good explanation of the difference between Style Triggers and Data Triggers:

SO said:

A regular trigger only responds to dependency properties.

A data trigger can be triggered by any .NET property (by setting its Binding property). However, its setters can still target only dependency properties.

SO topic

MSDN said:

Dependency Property Class:

Represents a property that can be set through methods such as, styling, data binding, animation, and inheritance.

These definitions seem recursive to me! I'm going to use a simpler distinction (for the purpose of this tutorial):

Style Triggers respond to simple UI property changes. For anything more complicated we can turn to DataTriggers.
            <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, 
                AncestorType={x:Type Canvas}}, Path=IsMouseOver}" Value="True">
                <Setter Property="Data" Value="{DynamicResource geoFrown}" />
            </DataTrigger>


(Note the use, or re-use, of the DynamicResource 'geoFrown'.)

DataTriggers allow us to specify the when, where and what of the trigger. For our specific example they allow us to attach the trigger to the containing Canvas-element, but cause a change to the child (Path) element. This introduces the concept of routed events, discussed in Part 3.

It should be possible to get this to work in the opposite direction, adding the Trigger to the Canvas and using TargetName to locate the Path (named 'smile') within the Canvas.

Expander and Transforms

These two topics aren't related, and I don't discuss them in any detail. I put a copy of the smiley-face in an Expander control... because we can! I use a SkewTransform to skew (to distort/twist) the smiley-face.
            </Canvas>
            <Expander Grid.Row="2" Grid.Column="5" Grid.RowSpan="2" Grid.ColumnSpan="2" 
                       Header="Another smile" IsExpanded="False">
                <Canvas Background="Transparent">
                    <Canvas.LayoutTransform>
                        <SkewTransform AngleX="10" AngleY="10" />
                    </Canvas.LayoutTransform>
                    <Ellipse Canvas.Left="0" Canvas.Top="0" Width="80" Height="80" 
                         Stroke="Blue" StrokeThickness="4" Fill="Yellow" />
                    <Ellipse Canvas.Left="15" Canvas.Top="15" Width="20" Height="20" 
                         Stroke="Blue" StrokeThickness="3" Fill="White" />
                    <Ellipse Canvas.Left="20" Canvas.Top="25" Width="6" Height="5" Fill="Black" />
                    <Ellipse Canvas.Left="45" Canvas.Top="15" Width="20" Height="20" 
                         Stroke="Blue" StrokeThickness="3" Fill="White" />
                    <Ellipse Canvas.Left="50" Canvas.Top="25" Width="6" Height="5" Fill="Black" />
                    <Path Stroke="Blue" StrokeThickness="4" Data="M 20,54 Q 40,80 60,54" />
                </Canvas>
            </Expander>


Expander Overview :MSDN
Transforms Overview :MSDN

I changed the position of the various ellipses, and the path coords, so that the face doesn't appear too far down the window. It may instead have been possible to move the entire face, perhaps using a TranslateTransform, although it may have required placing the face in another control such as a Rectangle. You might investigate this.

Rather than copy all the code for the smiley-face it could be created as either a custom (user) control, or even perhaps as a Resource (or set of Resources).

We can rotate and skew at the same time using a TransformGroup:
        <Canvas.LayoutTransform>
            <!--<SkewTransform AngleX="10" AngleY="10" />-->
            <TransformGroup>
                <RotateTransform Angle="40" />
                <SkewTransform AngleX="10" AngleY="10" />
            </TransformGroup>
        </Canvas.LayoutTransform>





The tutorial continues in Part 3, which introduces code.

Here is the full code for the Window:

Spoiler

This post has been edited by andrewsw: 07 May 2014 - 12:47 PM


Is This A Good Question/Topic? 1
  • +

Replies To: WPF Build a Window 2, more XAML

#2 theZ  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 3
  • Joined: 18-February 14

Posted 27 November 2014 - 07:14 AM

Once again, "Thank you"! I finally found simple, detailed and very useful tutorial I needed to learn the WPF basics!
Was This Post Helpful? 0
  • +
  • -

#3 andrewsw  Icon User is offline

  • It's just been revoked!
  • member icon

Reputation: 3808
  • View blog
  • Posts: 13,508
  • Joined: 12-December 12

Posted 27 November 2014 - 10:11 AM

Thank you,this is nice to hear.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1