Blog Archives

WPF: Accessing Resources From An External Project

A friend asked me recently if it was possible to access resources that were created and stored in a separate assembly. Although I was sure it could be done, I’d never tried it, so I was interested in experimenting with this idea.  It turns out that the Pack URL holds the key.  Here’s an example.

Access a single ResourceDictionary

In a ResourceDictionary file named Dictionary1.xaml inside a standard WPF Application project named ‘Resources’, I have the following markup:

 

    1 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    2    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    3     <LinearGradientBrush x:Key="MyBrush">

    4         <GradientStop Color="Black" Offset="0"/>

    5         <GradientStop Color="Yellow" Offset="1"/>

    6     </LinearGradientBrush>

    7 

    8   <Style x:Key="RedButton" TargetType="{x:Type Button}">

    9     <Setter Property="Background" Value="Red"/>

   10     <Setter Property="Foreground" Value="Yellow"/>

   11     <Setter Property="Margin" Value="12,3"/>

   12     <Setter Property="Padding" Value="3"/>

   13     <Setter Property="MinWidth" Value="75"/>

   14     <Setter Property="MinHeight" Value="34"/>

   15   </Style>

   16 </ResourceDictionary>

 

Very basic resources, I know, but sufficient to demo the process. The next step is to create the ‘client’ project, the one that will use this resource.  You can either add a new project to the current solution or start a completely separate solution. In my first example, I added a WPF Application project to the solution and named it ‘ResourceUser’.

In either case, you need to add a reference to the ‘Resource’ project in the Reference section of the ResourceUser project. If you add a second project to the current solution, the project name will be available to you in the Projects tab of the Add reference dialog (as shown in the screenshot below).  If you use a separate solution, you’ll have to use the Browse tab and navigate your way to the required assembly.

 

ResDictonaries001

 

This enables you to point to the source file, i.e. the ResourceDictionary named Dictionary1.xaml in the Resource project.  The way you point to that resource is to create a ResourceDictionary in the Application.xaml file of the client project, the project that I named ResourceUser.  You set its Source property to the Dictionary1.xaml file.  As I said at the start, you use the pack URL syntax to do this. Here’s the syntax that does the job:

 

    1 <Application x:Class="Application"

    2    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    3    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    4    StartupUri="Window1.xaml">

    5     <Application.Resources>

    6 

    7         <ResourceDictionary Source="pack://application:,,,/Resources;component/Dictionary1.xaml"/>

    8 

    9   </Application.Resources>

   10 </Application>

 

To avoid confusion, I probably should have named the first project something like ‘ResourceDictionarySource’, but anyway I’m sure you’ll understand that the word ‘Resources’ in that Source property refers to the name of the project.  You’ll also see that the Dictionary1.xaml file name is included as the final item.  The general syntax for the pack URL approach is:

application:,,,/<Name of project you are referencing>;component/<Name of specific file you want to access>

Once you’ve rebuilt the project, you can use the referenced resources just as you would if they existed in the client project.  To use the gradient brush and the button styles from my example above, you can do something like this in the Window of the ResourceUser project:

 

   12     <Button Style="{StaticResource RedButton}" Content="Demo" Margin="52,21,127,0" Grid.Row="1" Height="22" VerticalAlignment="Top" />

   13     <Rectangle Margin="52,28,127,17" Name="Rectangle1" Stroke="Black"

   14               Fill="{StaticResource MyBrush}"/>

Access multiple ResourceDictionaries

If the project that has the resources contains more than one ResourceDictionary, then you can use a MergedDictionaries block in the Application.xaml file of the client project. The following example assumes that the Resources project now contains two separate ResourceDictionary files named Dictionary1.xaml and Dictionary2.xaml:

 

    5     <Application.Resources>

    6 

    7     <ResourceDictionary>

    8       <ResourceDictionary.MergedDictionaries>

    9         <ResourceDictionary Source="pack://application:,,,/Resources;component/Dictionary1.xaml"/>

   10         <ResourceDictionary Source="pack://application:,,,/Resources;component/Dictionary2.xaml"/>

   11       </ResourceDictionary.MergedDictionaries>

   12     </ResourceDictionary>

   13 

   14   </Application.Resources>

 

What if you have ResourceDictionaries in the client project too?

This doesn’t present any kind of problem.  You simply add the ResourceDictionary to the MergedDictionaries collection in the usual way.  For example, if you have a ResourceDictionary named ResourceDictionary3.xaml in the ResourceUser project, you can use this syntax in Application.xaml:

 

    7     <ResourceDictionary>

    8       <ResourceDictionary.MergedDictionaries>

    9         <ResourceDictionary Source="pack://application:,,,/Resources;component/Dictionary1.xaml"/>

   10         <ResourceDictionary Source="pack://application:,,,/Resources;component/Dictionary2.xaml"/>

   11         <ResourceDictionary Source="Dictionary3.xaml" />

   12       </ResourceDictionary.MergedDictionaries>

   13     </ResourceDictionary>

 

 

What about resources that use their own resources?

So what happens if for example you have a ControlTemplate that uses images that are stored as resources in the Resource project?  Well, this isn’t a problem at all because the whole Resource project is referenced, so if the template calls for a resource file in that project when it is used in the client project, that file is available.

If that explanation isn’t as clear as you’d like, here’s an example.  In the Dictionary2.xaml ResourceDictionary, you have a Control Template for a CheckBox that uses three images.  Here’s the first part of the markup for the CheckBox Control Template:

 

    6 <ControlTemplate x:Key="ThumbCheckBox" TargetType="{x:Type CheckBox}">

    7         <Border Name="OuterBorder"

    8               BorderBrush="Black" BorderThickness="1">

    9             <Canvas Name="MainCanvas">

   10                 <Border Name="ImagesBorder" BorderBrush="DarkGray" BorderThickness="1"

   11                       Width="24" Height="32" Margin="1,1,0,0" >

   12                     <Canvas Name="ImagesCanvas">

   13                         <Image Name="ThumbDown" Source="ThumbDownSmall.jpg" Width="18" Height="26"

   14                             Canvas.Left="2" Canvas.Top="2"/>

   15                         <Image Name="ThumbUp" Source="ThumbUpSmall.jpg" Width="18" Height="26"

   16                              Margin="1"  Canvas.Left="2" Canvas.Top="2"

   17                              Visibility="Hidden"/>

   18                         <Image Name="Indeterminate" Source="QuestionMark.jpg" Width="18" Height="26"

   19                              Margin="1"  Canvas.Left="2" Canvas.Top="2"

   20                              Visibility="Hidden"/>

   21                     </Canvas>

 

You can see that there are three jpgs involved in the creation of that template.

They are all stored in the project with a Build Action of Resource.  One of the images is named ThumbDownSmall.jpg  and you can see it shown in the Solution Explorer, together with its properties in the Properties pane:

ResDictonaries002

 

If you assign this ControlTemplate to a CheckBox instance in the Window of the client project, the required images will be pulled from the Resources project as and when they are needed.  The following XAML in the client project uses the template:

 

    <CheckBox Name="chkApproval" Margin="52,14,111,36" Grid.Row="2"

         Template="{StaticResource ThumbCheckBox}"

         Content="Not Approved"

         IsChecked="{x:Null}"

         IsThreeState="True">

    </CheckBox>

 

And the result is the CheckBox you can see at the bottom of the Window:

 

ResDictonaries003

You can download the sample solution I used for this blog post.  It's available as a blog attachment or can be downloaded from here.

Moving From WinForms to WPF

I’ve written lots of blog posts about WPF and in most cases the selection of topics has been a bit random.   Essentially, if it interests me at the time I’d write about it.  The problem with that approach is that it might assume a more basic grasp of WPF than many readers have.

In a recent post on VB City, someone said (in a discussion about WPF): “I don't know where to start and a WPF primer would be good”.  Not knowing where to start is probably a common problem, so I plan to write a few blog posts that will take a WPF 101 approach.  I’ll still be writing the higher level blogs in between times, but hopefully this series will be useful to those who are familiar with WinForms and want to see the specific differences between WinForms and WPF.

To make the blogs’ content accessible to the widest range of readers, I’ll base the samples on the VB Express version.  Today we’re still at VB2008, but VB2010 will be along in a few weeks and I’ll switch to that when it happens. I’ll also try and flag up any important differences between the 2008 and 2010 versions.

So let’s get started.

Create a new WPF project

Start a new project and select the WPF Application template:

WPF101_1_001

I’ve named it WPF101_1.

Check out the IDE

The view in the IDE is similar to the one for WinForms, but there are some obvious differences.

WPF101_1_002

The first major change from WinForms is the way that the design area is split.  The top half still shows the design view of the ‘Form’ (now a Window in WPF, but very similar in role to a Form). Below that, there’s the XAML pane.

XAML is a version of XML that you can use in WPF to design elements. We’ll be looking at XAML in much more detail as we progress, but for now remember that you don’t have to be an expert in XAML to be able to create some very useful and good WPF applications. In fact you could create everything using Visual Basic, although this isn’t the most efficient way of doing it.

The list of items in the Solution Explorer is also slightly different. There are new file types listed – specifically by default Application.xaml and Window1.xaml.  Both those XAML files have a plus sign next to them. When you click on the plus sign, a second file with the extension of xaml.vb appears. In general, the role of the XAML files is to hold the markup that represents the look of the elements in the project. The xaml.vb file, which is usually referred to as the code-behind file, generally deals with the behaviour. Their roles are a lot more flexible than that, but it’s a reasonable way of thinking of them at this early stage.

The Toolbox at the left hand side of the IDE is similar to the WinForms one.

WPF101_1_003

Many of the names of the controls are the same in both WinForms and WPF. This is both a good and bad thing. It’s good because you’ll be familiar with them. It’s bad because there are often differences that will catch you out if you’re not aware of them.

Create a Hello World! Window

1.  Add a Button to the Window

As a start point, I’ll create the canonical Hello World! app.  From the ‘Common’ tab of the Toolbox, select and drag a Button on to the surface of the Window. A key difference between WPF and Winforms when you do this is that you will see that some additional XAML is added to the XAML pane below the Designer.

WPF101_1_004

Your version won’t be exactly the same. The Margin values, for instance, are likely to be different. Also, I’ve split up the original into three lines just to reduce the width of the screenshot.

The XAML markup you see there between the opening and closing tags creates an instance of a Button. Here’s the Button in the Design pane.

WPF101_1_005

 

Looking at the things you see in the Design and XAML panes, these bring up various WPF specific points :

Nested Content  There are two blue bars at the left and top of the Window, each with a number in them. To understand what these represent, you need to look back at the XAML that was automatically created for this Window. On line 5 you see there is an opening tag for a Grid. On Line 9, there is a closing tag for this Grid. Whenever you create a new Window in the IDE, a Grid will be placed inside the Window in this way. It’s placed there is to act as a container for the remaining content.

One of the features of WPF is that many of the visual elements can only contain a single child. Now, although you may think it’s bizarre at this stage, a WPF Window is one of the elements that can contain only one child element. If that were literally true, the options for useful Windows and user-friendly applications would be very limited. It is true, but the get out clause is that each child element may content sub-elements. And each sub-element may contain its own children and so on. Therefore this isn’t really a limitation at all. We’ll look at why WPF layout is structured in this way later.

In the example so far, the Window has a child element of a Grid and the Grid has a Button as its child.

Grid Markers  Going back to the blue bars, these mark the outside edge of the Grid.  The values represent the height and width of the Grid. You will see in a later blog post how you can the Grid’s blue bars to add columns and rows to a grid.

Resolution Independence   The Button has been given a fixed Height value of 23 and a Width of 75 pixels. You should note that these aren’t the standard screen pixels that you are used to in Winforms. They are what is known as ‘device independent units’.  Confusingly, they are often referred to as ‘device independent pixels’, which almost inevitably results in them sometimes being referred to as just plain old ‘pixels’. But they are different from the pixels of your Winforms applications.

The device independent pixel of WPF respects the Dots Per Inch (DPI) setting of the system it is using. What this means in reality is that if you create your WPF button and deploy the application to a system that has a higher DPI setting, WPF will use more than ‘your’ 23 and 75 pixels to render it.   The effect of this is that the button will be as crisp on a machine with a different DPI setting as it is on yours. You don’t get that scrunching up of the elements that you often see in this situation. 

What happens is that WPF’s rendering engine makes full use of the available pixels on the deployed system. So if the machine the application is deployed on has a System DPI of, say, 120 then WPF would use 29 of those higher density pixels for the height and 94 for the width (approximately), rather than the original 23 and 75.   This is different from the blocky “fill in the extra pixels and get a jaggy result” effect that you will have seen elsewhere. Because WPF renders everything on screen using vector graphics, you simply get a higher density version of the original.

Margins   The XAML markup shows the Margin property for the Button. The four values represent the distance in device independent units that this button will always be from the edges of its parent. In the current example, it is 48 units from the left edge of the Grid and 40 units from the top. If you drag the button around in the Design pane, then let go, you’ll see that the values of the Margin property settings will change to reflect where you’ve left the Button.  The four values in the Margin setting represent Left, Top, Right, Bottom – always in that order. 

Alignment   You’ll also have noticed the two arrow pointing from the Button to the edge of the Grid. These signify that the Button is horizontally aligned to the left and vertically aligned to the top. This is also confirmed in the properties shown in the XAML. You can think of this as being the same as the Anchor properties in Winforms. WPF however offers much more subtle options than just plain anchoring.

if you did try dragging the button around in the Design pane, you’ll probably have found that, quite confusingly, not only do the top and left margin values change, but you might also see either the HorizontalAlignment, the VerticalAlignment, or both of them, disappear from the XAML. The reason for this is that WPF has a very sophisticated layout toolset that you can use directly in the Design pane. (You can of course manually make changes to the XAML, but because this is Winforms –> WPF, I’m going to try and use minimal hand coding of XAML.)

Looking at my original screenshot of the Window, you’ll see that at the right and bottom of the Button there is a small circle. Hover over the one at the right and you’ll see a hand icon appear.  It can be a bit fiddly, so make sure you have the hand, not the double-headed arrow.  If necessary, use the excellent Zoom scaling tool in the top left hand corner of the Design pane to increase the size of the display.

WPF101_1_006

 

Now, click on the circle and three things will happen. The circle will go and be replaced by another arrow, this one pointing to the right hand edge of the grid.  The third value in the Margin property down in the XAML pane will change from  to a new value.  Finally, the HorizontalAlignment property will disappear from the XAML.

The result of this change will be that if you run the project and change the size of the Window, the Button’s width will change so that it is always the same distance from the left and right edges. So at this point you have three-way anchoring.

Vertically, there will be no change if you alter the size of the Window, because the HorizontalAlignment is still set to Top and there is a constant Margin of 39 units between the top of the Button and the top of the Grid. Obviously, if you click on the circle at the bottom of the button, this will make a similar change to the vertical settings.

If you click on any of the arrows, they will change to circles.  This is a feature you need to experiment with in order to become totally familiar with it.

Button Content  You’ll see the word ‘Button’ located between the opening and closing tags of the Button element markup. The same word also appears on the face of the button in the Design pane. In a similar way to how Winforms assigns a default Text property of ‘Button1’ to a button, WPF assigns a default Content property of a string containing the word ‘Button’.  Note that it’s not a Text property, as in Winforms, even though in this case it does happen to be text.  In WPF, Content is everything. A WPF button can have almost any content. The limitations of text and an image are gone. If you remember my explanation above of nested content, this applies equally to buttons. So a button can contain layers of nested content. I’ll be covering the possibilities of this later, but for now just keep in mind that it’s Content and not Text.

WPF controls have default properties and the default property of a button is Content. Because of this, the XAML is displayed in the way you first saw above. I think the IDE maybe does you a disservice here by doing this. Another way of more specifically identifying the Content is to include its property value within the opening tag:

WPF101_1_007

 

So, even with something as basic as a button, you’ll see that there are already important differences between Winforms and WPF.

2.  Add a TextBlock to the Window

Expand the ‘Controls’ tab in the Toolbox and scroll down until you reach the TextBlock. Drag one on to the Window’s surface.

WPF101_1_008

 

Again you will see the alignment arrows and, as before with the button, you change these settings.

The default Property of a TextBlock is the Text property. When first created, the TextBlock is empty. You can enter text for the TextBlock either by placing it between the opening and closing tags or by specifically assigning a value to the Text property.

 <TextBlock Height="21" Margin="39,0,62,38" Name="TextBlock1"  VerticalAlignment="Bottom" Text="This is a TextBlock" /> 

For the sake of completeness, I should tell you that it is possible to include other sub-elements inside a TextBlock. And these don’t even have to be text. That said, there are many better options for cases where you want to mix text and other graphical elements, so my advice is to stick to using the TextBlock for plain text. Well, not plain text – WPF offers you a lot of options for enhancing the look and style of text.  I’ll cover these choices later.

The WPF TextBlock is a Read Only control. If you want to take text input from a user, a TextBox, similar to the WinForms one, is available. You may have noticed that there is a Label element available in the Toolbox. I could have used this and it would have worked fine, but I chose the TextBlock because for most purposes in WPF apps it’s a better option.

3.   Add the Button’s Event Handling Code

In many cases, you can do what you probably do with a Winforms button in order to create and access its Click event. You double-click on the button in the Design pane.  This will automatically create the event handler code which include the familiar ‘Handles’ clause at the end of the signature line. In this example, ‘Handles Button1.Click’. It also brings up the xaml.vb code-behind file in the IDE to allow you to start adding code.

There is also an alternative. Back in the XAML for the Button, you can add a Click property inside the Button’s opening tag. When type in ‘Click=’ you will get a context menu:

 

WPF101_1_009 

 

In the above screenshot, the menu only contains the one choice of New Event Handler. If the code-behind file contains other procedures, these will also be listed as choices. When you double-click on the New Event handler choice an event handler will be created for you in the code-behind. This procedure won’t include the Handles clause.  The Handles clause isn’t needed for our Button1 instance, because it is already wired up via the Click property in the XAML.

As for the VB code in the event handler, this will be:

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)

        TextBlock1.Text = "Hello World!"

    End Sub

Apart from the new WPF control, the syntax will be familiar, i.e. assigning a value to the Text property. The code differences between Winforms and WPF unfortunately won’t always be this simple, but I’ll try and cover many of the Gotchas as we come to them.

Change the Heading of the Window

In Windows Forms, you set the Text property of a Form to change what is shown as its heading. In WPF, the Window has a Title property that does the same thing. You’ll find this within the opening tag of the Window in the XAML pane.

WPF101_1_010

You can edit it there or you can select the Window in either the Design or XAML panes and then change the value of the Title property in the Properties pane. 

Run the Project

When you run the project and click on the Button, the event handler in the code-behind will populate the TextBlock with the Hello World string.

WPF101_1_011

Other Points

  Reloading.  You’ll often find that you see a gold bar at the top of the Design pane that says “An assembly or related document has been updated which requires the designer to be reloaded. Click here to reload”. You need to click on this before continuing. This message will appear whenever you make a change (usually in the code-behind or elsewhere in the project) and the layout engine needs to figure out how that affects what it’s going to show you as a result of the change. 

  Properties Pane.   The Properties pane is available to you in the same way as in Winforms.  You can click on an element either in the Design pane or the XAML pane and its properties will appear in the Properties pane, where you can view or edit them.  So you don’t even need to edit the XAML in most cases if you’re more comfortable using the Properties pane. 

 

 

 

Summary

In this first article, you’ve seen some of the differences between WPF and Windows Forms. There’s still an awfully long way to go and many differences to learn about, but at least we’ve made a start.

Some coverage of the following topics has been included:

 

  • Alignment
  • Button
  • Code-behind
  • Event Handling
  • Grid
  • Label
  • Margins
  • Nested content
  • Properties Pane
  • Reloading Message
  • Resolution Independence
  • TextBlock
  • TextBox
  • Window Title
  • XAML pane

Implementing Edit and Format Features in a WPF RichTextBox

In previous blogs, I looked at how to access a plain text file through a RichTextBox and then how to open and save content in RTF format.   In the second blog, I also looked at some core editing features which are available as keyboard shortcuts.

This time I want to look at how to create a simple text editor based around the RichTextBox.  I also want to cover the scenario where you load a file of one type, such as a plain  text file, make changes and then save it as an RTF file.

Because there is quite a lot of content here, I’ve numbered the sections and listed them:

  1. Introduction to creating a WPF User Control in a library
  2. The skeleton of the RTBEditor user control
  3. Referencing the user control from another project
  4. Creating the File Menu Item of the user control
  5. Creating and coding the File >New menu item
  6. Creating and coding the File > Open menu item.
  7. Creating and coding the File > Save As Text menu item
  8. Creating and coding the File > Save As Rtf menu item
  9. Creating and coding the File > Exit menu item
  10. Creating the Edit menu item
  11. Creating the Format and Align menu items

 

1.  Creating a WPF User Control

Instead of adding a RichTextBox directly into a Window, I’m going to create a WPF UserControl.   In the interests of reusability and portability, I’ll store this in a WPF User Control Library.  To do this from your currently open WPF Solution:

  1. Select File > Add > New Project in the Visual Studio IDE main menu.
  2. Select ‘Windows’ from the list of Project types.
  3. Select WPF User Control Library from the list of templates.
  4. Rename it.  I named the library ‘WPFUserControls’.
  5. Click OK.

To add a new User Control to the library:

  1. Select Project > Add New Item from the IDE menu.
  2. Select WPF from the Categories list.
  3. Select User Control (WPF) from the Templates list.
  4. Rename it.  I named it RTBEditor.xaml.
  5. Click ‘Add’.

2.  Skeleton of the User Control

To create the skeleton of the User Control, do the following:

  1. Delete the default Grid in the UserControl markup.
  2. Replace it with a DockPanel.
  3. Inside the DockPanel, add a Menu element.
  4. Set its DockPanel.Dock property to Top.
  5. Set its MinHeight property to 25.
  6. Inside the DockPanel, but outside the markup for the Menu, add a RichTextBox.
  7. Name it ‘RTB’.
  8. Set its DockPanel.Dock property to Bottom.
  9. Set its MinHeight and MinWidth properties to 200.

The markup below includes the steps described above, plus a few other less important property settings I’m using for demo purposes:

  <DockPanel>

    <Menu Margin="0,0,0,5" Name="Menu1" DockPanel.Dock="Top" MinHeight="25" >

 

    </Menu>

    <RichTextBox Name="RTB" BorderThickness="2" DockPanel.Dock="Bottom"

                MinHeight="200" MinWidth="200"

                 Background="#FFEDEAEA">

    </RichTextBox>

  </DockPanel>

3.  Referencing the User Control

When I’m building a User Control, I like to insert an instance of it in a Window, so I can see how things are progressing.

Because the User Control is stored in a library, it’s necessary to reference this library from any project that wants to use it.    In the Solution I’m using, I have two projects; the Library, named WPFUserControls and a standard WPF project named WpfRichTextBox.   You can see this from the screenshot below:

RTB003

I need to add a reference to the library in the WPF Window Application project so that I can access and use the RTBEditor user control in one or more Windows.  To do this:

  1. Ensure you have the WPF Window Application project selected in the Solution Explorer.
  2. Select Project > Add Reference form the IDE main menu.
  3. In the Add Reference dialog box, click on the Projects tab.
  4. There will only be one Project Name in the list, so ensure this is selected and click OK.

In WPF, another step is needed.  You have to add details of the namespace to the markup of the Window where you want to use the user control.   I’m using a file named Window2 to host the text editor control, so I need to add the following markup to the xml namespaces at the top of the file:

xmlns:uc="clr-namespace:WPFUserControls;assembly=WPFUserControls"

You can type that in by hand if you want to, but it’s not the easiest syntax to remember.  Far easier is to type as far as the “uc=” point and then use the Intellisense list to select the library project. 

RTB004

In this case, you want to select the ‘WPFUserControls in assembly WPFUserControls’ item, because this is the name of the project you are referencing.   When you click on it, the correct syntax, as shown in the earlier XAML snippet, will be created for you automatically.

With this in place, you can add an instance of the user control to the Window.  Here’s the markup for the Window so far:

 

    1 <Window x:Class="Window2"

    2    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    3    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    4    xmlns:uc="clr-namespace:WPFUserControls;assembly=WPFUserControls"

    5    Title="Using the Text Editor " Height="500" Width="400">

    6 

    7   <Grid>

    8     <uc:RTBEditor />

    9 

   10   </Grid>

   11 </Window>

There’s not much to see yet, but we can build up the user control and watch its progress.

4.  The File Menu of the User Control

WPF Menus are mainly composed of MenuItems.   The two key properties of a MenuItem are its Header (the text the user sees)  and its Click property (the link to the event handler for the Click event in the code-behind).

MenuItems can be nested, so in this case several sub items are nested inside the main File menu.  The following XAML placed inside the Menu1 Menu will create the standard list of File items:

    6       <MenuItem Header="_File" Name="FileMenuItem" >

    7         <MenuItem Header="_New" Name="mnuItemNew" Click="mnuItemNew_Click" />

    8         <MenuItem Header="_Open" Name="mnuItemOpen" Click="mnuItemOpen_Click" />

    9         <MenuItem Header="Save as _Text" Name="mnuItemTxtSave" Click="mnuItemTxtSave_Click" />

   10         <MenuItem Header="Save as _RTF" Name="mnuItemRTFSave" Click="mnuItemRTFSave_Click" />

   11         <Separator />

   12         <MenuItem Header="E_xit" Name="mnuExit" Click="mnuExit_Click" />

   13       </MenuItem>

The only points to note here are that you can use accelerator keys, such as Alt & F for the File Menu, or – more usefully – Alt & F & N for File > New.   Placing the underscore immediately in front of the letter you want as the hotkey is all that’s needed.
You’ll see that I also added a Separator element between the Exit menu item and the others.

5.   The New File Item

For our purposes, we don’t really need to create a new file at this point.  What the user wants is a blank RichTextBox in which to enter some content.   So we can just the use the Clear method to do this:

    Private Sub mnuItemNew_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)

        RTB.Document.Blocks.Clear()

    End Sub

In a production application, you would of course want to build in a feature that asks the user to confirm what is effectively a total deletion of the current content. 

6.  The Open File Item

WPF currently doesn’t have its own OpenFileDialog, but you can use the old Win32 version.  Add the following two Imports statements to the code-behind:

Imports Microsoft.Win32

Imports System.IO

The first is for the OpenFileDialog; the second for the FileStream I’m just about to use to open and read the file.

Here is the code for this menu item:

   13     Private Sub mnuItemOpen_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)

   14         'Let the user select a file

   15         Dim ofd As New OpenFileDialog

   16         With ofd

   17             '  Set preferred file types

   18             .Filter = "Text Files(*.txt;*.rtf)|*.txt;*.rtf" & _

   19             "| All files (*.*)|*.*"

   20 

   21             If .ShowDialog() = True Then

   22                 '  Identify the chosen file extension

   23                 Dim dataformat As String

   24                 Select .FileName.Substring(.FileName.Length – 3)

   25                     Case "rtf"

   26                         dataformat = DataFormats.Rtf

   27                     Case Else

   28                         dataformat = DataFormats.Text

   29                 End Select

   30 

   31                 '  Then load the file using the appropriate data format

   32                 Dim fs As New FileStream(.FileName, FileMode.Open, FileAccess.Read)

   33                 Using fs

   34                     'Create a TextRange that comprises the start and end points of the RichTextBox text

   35                     Dim RTBText As New TextRange(RTB.Document.ContentStart, RTB.Document.ContentEnd)

   36                     RTBText.Load(fs, dataformat)

   37                 End Using

   38             End If

   39         End With

   40     End Sub

The OpenFileDialog code is straightforward and is no different from what you do in Windows Forms.   The Filter aims to encourage users to select text files that are editable in the RichTextBox, but allows them the freedom to choose others.   If they choose anything other than an Rtf file, the data format is set to Text.   This will allow actual text format files to be displayed properly; any others will simply display their binary form content.

The file load code is the same as I used in the earlier blogs, except that I use a variable named ‘dataformat’ instead of passing in the actual DataFormats object as the second parameter of the Load method of the TextRange.

7.  The Save As Text MenuItem

The code to save the content to a .txt file is very similar to that used in the earlier blog.  The only difference is that again I have added a file dialog to give the user the choice of file name and save location.    This time, of course, it’s a SaveFileDialog. 

   42     Private Sub mnuItemTxtSave_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)

   43         Dim sfd As New SaveFileDialog

   44         sfd.Filter = "Text Files(*.txt)|*.txt"

   45 

   46         If sfd.ShowDialog = True Then

   47             Dim fs As FileStream = File.OpenWrite(sfd.FileName)

   48             Using fs

   49                 Dim RTBText As New TextRange(RTB.Document.ContentStart, RTB.Document.ContentEnd)

   50                 RTBText.Save(fs, DataFormats.Text)

   51             End Using

   52         End If

   53     End Sub

 

8.  The Save As RTF Menu Item

It isn’t really necessary to have two separate procedures for the two different formats.    The above procedure can easily be edited to identify which of the menu items called it and then switch the Filter of the SaveFileDialog and the DataFormats value accordingly.  But for now we’ll just live with a bit of unnecessary duplication:

   55     Private Sub mnuItemRTFSave_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)

   56         Dim sfd As New SaveFileDialog

   57         sfd.Filter = "Rtf Files(*.Rtf)|*.Rtf"

   58 

   59         If sfd.ShowDialog = True Then

   60             Dim fs As FileStream = File.OpenWrite(sfd.FileName)

   61             Using fs

   62                 Dim RTBText As New TextRange(RTB.Document.ContentStart, RTB.Document.ContentEnd)

   63                 RTBText.Save(fs, DataFormats.Rtf)

   64             End Using

   65         End If

   66     End Sub

9.  The Exit MenuItem

I’ll be honest: I included this out of habit and then realised that this is a user control, not a Window or a Form.   So you wouldn’t usually want the Exit menu item to unilaterally close down the parent container.  So in this case I’ll use ‘Exit’ as meaning ‘Clear the current content’.   In which case, this now familiar Clear method will do nicely:

   68     Private Sub mnuExit_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)

   69         RTB.Document.Blocks.Clear()

   70     End Sub

10.   The Edit Menu Item

This is another container for nested menu items.   For this example, I’ve chosen to use the Cut, Copy and Paste commands.  Here’s the markup:

   14      <MenuItem Header="_Edit">

   15         <MenuItem Header="Cut" Command="Cut"/>

   16         <MenuItem Header="Copy" Command="Copy" />

   17         <MenuItem Header="Paste" Command="Paste" />

   18         <Separator />

   19         <MenuItem Header="Undo" Command="Undo" />

   20         <MenuItem Header="Redo" Command="Redo" />

   21       </MenuItem>

You’ll notice that I haven’t used the underscores with these menu items.  I could have, but as you will see if you run this project you already have the standard Ctrl & X, Ctrl & C, etc and I thought that a different set with Alt & whatever would just be confusing.

There are several key things to notice in this markup.   The first is that there is no Click property pointing to an event handler in the code-behind.   In fact, there is no code-behind needed for this functionality.  Because all five of these are built-in Application Commands, selecting any of them will cause that action to take place (maybe – more on this in a minute).   So, simply by adding the ‘Command=Undo’  to the markup, everything is in place to ensure that selecting that menu item will automatically cause the user’s last action to be undone.

Note that this is an Application wide command, so it will undo whatever user action the application was last aware of.  In other words, if this user control is surrounded by other controls in the Window and you apply an action to one of those other controls and then use the RTBEditor’s Undo command, it will undo the action you took on the other control.   It’s an application command, not a RichTextBox one.

Next, these commands are smart.  If it’s not appropriate for them to be available, then they won’t be.   What I mean by that is if, for example, you haven’t selected any content and so there is nothing on the clipboard, then the Paste command won’t be available.   The same logic applies to all these commands.   Here’s a screenshot:

RTB005

As you can see, there is no text in the RichTextBox and nothing has been selected.  Therefore the Cut and Copy commands (and their wired up menu items) are disabled.  In short, these commands know when they can or cannot execute.   (If you’re wondering, Paste is available because I had some other content still on the clipboard – it’s an Application wide command, remember).

Notice also that because these are Application commands with built-in shortcut key combinations, those combos are automatically displayed next to each menu item for you. 

11.   The Format and Align Menu Items

If you’ve groked the idea of how commands are used here, you’ll have no problem with the next two sets of menu items – Format and Align.   Here’s the markup:

 

   22       <MenuItem Header="Format">

   23         <MenuItem Header="_Bold" Command="{x:Static EditingCommands.ToggleBold}" />

   24         <MenuItem Header="_Italic" Command="{x:Static EditingCommands.ToggleItalic}" />

   25         <MenuItem Header="_Underline" Command="{x:Static EditingCommands.ToggleUnderline}" />

   26       </MenuItem>

   27       <MenuItem Header="Align">

   28         <MenuItem Header="Left" Command="{x:Static EditingCommands.AlignLeft}" />

   29         <MenuItem Header="Center" Command="{x:Static EditingCommands.AlignCenter}" />

   30         <MenuItem Header="Right" Command="{x:Static EditingCommands.AlignRight}" />

   31       </MenuItem>

The key difference here (and I’ll admit that it caught me out at first) is that you need to use a markup extension to get at these commands.   You can see that the first three commands have the prefix of ‘Toggle’, as in ‘Toggle Bold’, and so on.   And the commands do work in exactly that way.  Click them once to toggle the effect on, click again to toggle it off.   You can select text and apply one of the formats or you can toggle a setting and then begin to type.  The setting you have just made will then be applied.   That’s why you will find that they are not disabled even when nothing is selected.

You’ll see that the markup extension requires the fully qualified name of the command.   There are other groups of commands, other than ApplicationCommands and EditingCommands and I will be looking at some of those in the next blog.   I also plan to include other commands and features, such as the ability to colour text and tweak the fonts.   At some stage I also want to bring the whole thing completely into the world of WPF by using a FlowDocument as the content container inside the RichTextBox.

Summary

In the meantime, you have the makings of a useful text editor here.   There are many other Application and Editing commands and you may well want to add some of those to the menu.    WPF’s Commands can make many of these common tasks very easy to implement.

WPF: Using a VirtualizingStackPanel to Improve ComboBox Performance

Introduction
In this earlier blog, I looked at how to use a ComboBox to display a list of all the system fonts, displaying each font name in its own font style.

I mentioned there that fonts are something of a special case, in that this collection of fonts is automatically cached for you after you first use it. The result is that when you run the application for the second and subsequent times there is minimal delay between the time the user clicks the ComboBox down arrow and the appearance of the list of fonts.

In this blog I want to look at the situation where you have a ComboBox that has a large number of other (possibly graphically complex) items to display.
Without the built-in caching that is available for fonts, you will often find that the display of large amounts of data in a ComboBox can be annoyingly slow. Furthermore, it will continue to be slow each time the ComboBox is recreated (although you can re-access the ComboBox and have shorter delays as long as you don't close the Window). However, if you close the Window that contains this ComboBox, then the whole display has to be rebuilt and the initial longer delay will reoccur.

The reason for the delay is that WPF creates the complete display for the ComboBox in advance,  regardless of the fact that it might only display a tiny proportion of the total number of items. It is this initial unnecessary creation of visual content that causes the problem. Let's create a sample project to demonstrate this.

Display of Large Amount of Text
First, I'll create some fairly uninspiring demo data in the code-behind: 

    Private Sub Window2_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim DemoList As New List(Of String)

        For i As Integer = 0 To 10000

            DemoList.Add("This is ItemCollection # " & i.ToString)

        Next

        Me.DataContext = DemoList

    End Sub 

This simply creates 10,000 Strings, stores them in a List (Of String) and sets this List as the DataContext for the Window.

Next, in the markup for the Window, create a ComboBox that will use those Strings for its display of items: 

    <ComboBox Height="23" Margin="27,10,10,0" Name="ComboBox1"

             VerticalAlignment="Top"

             ItemsSource="{Binding}">

    </ComboBox>  

When you run this application, there will be noticeable delay before the list appears. As I mentioned above, when you click for a second and subsequent times, the delay is shorter but still exists. If you close this Window and then subsequently show it again, the long delay will reoccur.

Improving the Display Time
The quick fix for this issue is to insert a VirtualizingStackPanel into the template for the ComboBox. This panel has the ability to assess how many items can be displayed, based on the measurements of the ComboBox, and automatically creates the visuals for that limited number of items only.

An easy way to implement this is to create an ItemsPanelTemplate as a Resource and reference it in the ComboBox markup.  

  <Window.Resources>

    <ItemsPanelTemplate x:Key="VSP">

      <VirtualizingStackPanel/>

    </ItemsPanelTemplate>

  </Window.Resources>

 

 

    <ComboBox Height="23" Margin="27,10,10,0" Name="ComboBox1"

             VerticalAlignment="Top"

             ItemsSource="{Binding}"

             ItemsPanel="{StaticResource VSP}">

    </ComboBox>  

Specifically, the ItemsPanel property of the ComboBox is set to that ItemsPanelTemplate Resource.

If you prefer, you can include the VirtualizingStackPanel right in the ComboBox creation markup: 

   <ComboBox Height="23" Margin="27,10,10,0" Name="ComboBox1"

             VerticalAlignment="Top"

             ItemsSource="{Binding}"

            >

      <ComboBox.ItemsPanel>

        <ItemsPanelTemplate>

          <VirtualizingStackPanel />

        </ItemsPanelTemplate>

      </ComboBox.ItemsPanel>

    </ComboBox>  

Personally, I like to try and keep as much of these kind of things as Resources as I can.

More Complex Items

  If your display is more complex – maybe using a DataTemplate and including nested panels and some images in each item:

    <DataTemplate x:Key="ImgAndText">

      <StackPanel Orientation="Horizontal">

        <Image Source="Timer.jpg" Height="89" Margin="3"></Image>

        <StackPanel>

          <Image Source="Stars.jpg" Width="152" Height="44" Stretch="None"></Image>

          <TextBlock Text="{Binding}" Margin="2,15,2,2" ></TextBlock>

        </StackPanel>

       </StackPanel>

    </DataTemplate>

 

then the delay can become very substantial indeed and you will certainly want to be sure to include a VirtualizingStackPanel in the markup for the ComboBox.

WPF: Using a VirtualizingStackPanel to Improve ComboBox Performance

Introduction
In this earlier blog, I looked at how to use a ComboBox to display a list of all the system fonts, displaying each font name in its own font style.

I mentioned there that fonts are something of a special case, in that this collection of fonts is automatically cached for you after you first use it. The result is that when you run the application for the second and subsequent times there is minimal delay between the time the user clicks the ComboBox down arrow and the appearance of the list of fonts.

In this blog I want to look at the situation where you have a ComboBox that has a large number of other (possibly graphically complex) items to display.
Without the built-in caching that is available for fonts, you will often find that the display of large amounts of data in a ComboBox can be annoyingly slow. Furthermore, it will continue to be slow each time the ComboBox is recreated (although you can re-access the ComboBox and have shorter delays as long as you don't close the Window). However, if you close the Window that contains this ComboBox, then the whole display has to be rebuilt and the initial longer delay will reoccur.

The reason for the delay is that WPF creates the complete display for the ComboBox in advance,  regardless of the fact that it might only display a tiny proportion of the total number of items. It is this initial unnecessary creation of visual content that causes the problem. Let's create a sample project to demonstrate this.

Display of Large Amount of Text
First, I'll create some fairly uninspiring demo data in the code-behind: 

    Private Sub Window2_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim DemoList As New List(Of String)

        For i As Integer = 0 To 10000

            DemoList.Add("This is ItemCollection # " & i.ToString)

        Next

        Me.DataContext = DemoList

    End Sub 

This simply creates 10,000 Strings, stores them in a List (Of String) and sets this List as the DataContext for the Window.

Next, in the markup for the Window, create a ComboBox that will use those Strings for its display of items: 

    <ComboBox Height="23" Margin="27,10,10,0" Name="ComboBox1"

             VerticalAlignment="Top"

             ItemsSource="{Binding}">

    </ComboBox>  

When you run this application, there will be noticeable delay before the list appears. As I mentioned above, when you click for a second and subsequent times, the delay is shorter but still exists. If you close this Window and then subsequently show it again, the long delay will reoccur.

Improving the Display Time
The quick fix for this issue is to insert a VirtualizingStackPanel into the template for the ComboBox. This panel has the ability to assess how many items can be displayed, based on the measurements of the ComboBox, and automatically creates the visuals for that limited number of items only.

An easy way to implement this is to create an ItemsPanelTemplate as a Resource and reference it in the ComboBox markup.  

  <Window.Resources>

    <ItemsPanelTemplate x:Key="VSP">

      <VirtualizingStackPanel/>

    </ItemsPanelTemplate>

  </Window.Resources>

 

 

    <ComboBox Height="23" Margin="27,10,10,0" Name="ComboBox1"

             VerticalAlignment="Top"

             ItemsSource="{Binding}"

             ItemsPanel="{StaticResource VSP}">

    </ComboBox>  

Specifically, the ItemsPanel property of the ComboBox is set to that ItemsPanelTemplate Resource.

If you prefer, you can include the VirtualizingStackPanel right in the ComboBox creation markup: 

   <ComboBox Height="23" Margin="27,10,10,0" Name="ComboBox1"

             VerticalAlignment="Top"

             ItemsSource="{Binding}"

            >

      <ComboBox.ItemsPanel>

        <ItemsPanelTemplate>

          <VirtualizingStackPanel />

        </ItemsPanelTemplate>

      </ComboBox.ItemsPanel>

    </ComboBox>  

Personally, I like to try and keep as much of these kind of things as Resources as I can.

More Complex Items

  If your display is more complex – maybe using a DataTemplate and including nested panels and some images in each item:

    <DataTemplate x:Key="ImgAndText">

      <StackPanel Orientation="Horizontal">

        <Image Source="Timer.jpg" Height="89" Margin="3"></Image>

        <StackPanel>

          <Image Source="Stars.jpg" Width="152" Height="44" Stretch="None"></Image>

          <TextBlock Text="{Binding}" Margin="2,15,2,2" ></TextBlock>

        </StackPanel>

       </StackPanel>

    </DataTemplate>

 

then the delay can become very substantial indeed and you will certainly want to be sure to include a VirtualizingStackPanel in the markup for the ComboBox.

WPF: ProgressBar With Marquee Text Display and TemplateBinding

 Introduction
In this blog I want to demonstrate several things. The first is how to reveal a text message in a ProgressBar. This is easy to do and you can get reasonably close to the moving marquee effect that you can see on some digital displays.
A more important topic that I'll cover is TemplateBinding in ControlTemplates. This makes it easy to use a core template, but allow individual instances of the control type that use the template to tweak selected properties. In this case I'll use the FontSize property.
Finally, you sometimes come across a situation where the control (in this case the ProgressBar) doesn't have a property that maps directly to a property that exists in the ControlTemplate. Although this might seem improbable, it actually happens quite often. We'll look at how we can work round this limitation and in the example I will show you can set a Content property on a ProgressBar control, even though the ProgressBar doesn't actually possess such a property.

If you've read my last three or four blogs you may be thinking that I'm a bit obsessed with ProgressBars! That's not the case and it's simply that they represent a good case study for ControlTemplates, they require named parts and I think they make a nice change from the demos that mostly seem to use Buttons.

The ControlTemplate
In the previous blogs on ProgressBars, you will have seen that I have stored the ControlTemplates in the Window.Resources collection. This time, the ControlTemplate is going to be stored in the Application.Resources collection. The thinking behind this is that this template is designed to be reused in several Windows across the application, with different values being placed on some properties in some instances. So having it in a central location clearly makes it available application-wide and makes sense.

Here is the markup for the initial version of the ControlTemplate: 

    <ControlTemplate x:Key="PBWordReveal" TargetType="{x:Type ProgressBar}">

      <Grid>

        <Border Name="PART_Track" 

          BorderThickness="2" CornerRadius="5"

          Background="LightSkyBlue" BorderBrush="Navy"  />

 

        <ContentControl Name="PART_Indicator"

           Content="  Loading   . . . .   Loading   . . . . .   Loading"

           Margin="4,0" FontSize="14"

           Foreground="Black" Background="LightSkyBlue"

           VerticalAlignment="Center"

           HorizontalAlignment="Left"/>

      </Grid>

    </ControlTemplate> 

If you are not familiar with the concept of named parts – specifically the PART_Track and PART_Indicator shown above – you get an explanation in my previous ProgressBar blogs.

The outer track is a Border element, while the Indicator uses a ContentControl. The key property is probably the Content property, which as you can see contains the text that will be displayed. The HorizontalAlignment property is also quite important to the look of the final effect.

ProgressBar Instance
To use this template, I will create a ProgressBar instance in a Window. 

    <ProgressBar Name="PBReveal"

     Template="{StaticResource PBWordReveal}"

            Width="200" Height="40"    /> 

To animate the ProgressBar in the code-behind of the Window (in this example, a Window named 'WordReveal': 

Imports System.Windows.Media.Animation

 

Partial Public Class WordReveal

 

    Private Sub WordReveal_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

         Dim a As New DoubleAnimation

        a.From = 0

        a.To = 100

        a.Duration = New TimeSpan(0, 0, 6)

 

        PBReveal.BeginAnimation(ProgressBar.ValueProperty, a)

    End Sub

End Class 

When you run this project, the text content in the ProgressBar will be revealed from left to right. The screenshot shows the bar with the animation approx 60% complete:

And this is the display when the animation has ended:

Effectively all that is happening is that the ContentControl, the PART_Indicator element of the template, is revealed over the period of the animation – the revelation commencing from left to right.

If you want more of a marquee type effect, you can change the HorizontalAlignment : 

  HorizontalAlignment="Right" 

This will make the text scroll from right to left:

Setting the HorizontalAlignment to either Center or Stretch will produce a different result again.

TemplateBinding
TemplateBinding allows you to hand back control of selected properties to the instance of the ProgressBar that is using this ControlTemplate. It is simple to implement and in this example only requires the change shown below to the PART_Indicator named part of the template:  

       <ContentControl Name="PART_Indicator"

           Content="  Loading   . . . .   Loading   . . . . .   Loading"

           Margin="4,0"

              FontSize="{TemplateBinding FontSize}"

           Foreground="Black" Background="LightSkyBlue"

           VerticalAlignment="Center"

           HorizontalAlignment="Right"

          /> 

The TemplateBinding points to a specific property – FontSize. This can now be set in an instance of the control that uses the template:  

    <ProgressBar Name="PBReveal"

     Template="{StaticResource PBWordReveal}"

     Width="200" Height="40"

        FontSize="22"/> 

Note that if you choose not to set a value on the FontSize property in the ProgressBar instance, the default value for that property will be used (12).

Mapping Between Properties
As you can see from the markup, the PART_Indicator uses a ContentPresenter. The ContentPresenter has a Content property and this is what is used to set the value of the text to be displayed. The basic ProgressBar however doesn't have such a property. So if you want to include TemplateBinding to allow different text to be used in different instances that use this template, you need to map the ContentPresenter property to some other property of the ProgressBar.

In most cases, if you have only one property that needs this kind of mapping, the solution is simple. You can use the Tag property: 

        <ContentControl Name="PART_Indicator"

             Content="{TemplateBinding Tag}"

           Margin="4,0"

           FontSize="{TemplateBinding FontSize}"

           Foreground="Black" Background="LightSkyBlue"

           VerticalAlignment="Center"

           HorizontalAlignment="Right"

          /> 

And you insert the required text in the markup for the ProgressBar instance : 

    <ProgressBar Name="PBReveal"

     Template="{StaticResource PBWordReveal}"

     Width="200" Height="40"

     FontSize="22"

       Tag="  Please Wait . . . . . . . . . ."/> 

These two templated changes result in the following display:

As you can see, both the FontSize and the Content have changed.

If you need to include more than one of these kind of cross property mappings, there is nothing (apart from common sense and experience) stopping you from hijacking other ProgressBar properties. You can identify a property that you absolutely, categorically know won't be used (and there's the start of the slippery slope right there) you can use this to map to.

The more professional alternative, of course, is to create a custom control of your own which contains all the properties you desire. I'll be looking at the steps involved in creating a custom control in a future blog.

WPF: How to Create a Reversing ProgressBar

 

 

In some of my earlier blogs – here, here and here, I looked at some rather colorful, non-rectangular versions of the ProgressBar. Although you probably wouldn't want to use some of those versions outside of a game environment, I hope it did show you how you can create and tweak non-rectangular versions. In this blog, I want to examine a less bizarre variation and along the way hopefully show you some more useful WPF basics.

Reversing the Default ProgressBar
The default ProgressBar is rectangular, has slightly rounded corners, a Gray Background, Linear Gradient Green Foreground and, most importantly, it traverses from left to right. I was wondering about how to change this and have a ProgressBar that traversed from right to left – a kind of a countdown effect instead of the usual fill-increasing effect. Because I was in ControlTemplate mode, I confess that I initially overlooked the obvious solution – total simplicity in WPF – and created a whole new template that did the job. I'll show you the obvious WPF solution shortly, but first let's just look at how this can be done as a ControlTemplate task.

ControlTemplate
I set about this by using a combination of Expression Blend and Visual Studio. I know I could have done it all in Blend using the Edit Template tools, but I came up this WPF route the hard way, from back in the early days when there were no tools and you had to become a XAML-head to get anywhere with this new-fangled thing called WPF. So I find that I still prefer to create my ControlTemplates in XAML in Visual Studio and use Blend when I want to do anything fancy with graphics or animation.

If you find it easier to do it all in Blend, I have no problem with that, but here's my way. First create a ControlTemplate, Key it as 'ReverseBasicPB' and set its TargetType to ProgressBar. The visual elements of the template will all be held inside a Grid. 

    <ControlTemplate x:Key="ReverseBasicPB" TargetType="{x:Type ProgressBar}">

      <Grid>

 

      </Grid>

    </ControlTemplate> 

As you will know from the earlier blogs, the ProgressBar requires two named parts – PART_Track and PART_Indicator. In this version, PART_Track will consist of a Rectangle inside a Border and PART_Indicator will be a Rectangle with a reasonable copy of the Green Linear Brush.
(As a side note, it is possible to dig into the WPF namespaces and get hold of the ProgressBarGlassyHighlight and ProgressBarTopHighlight Brushes, but I was content to go with my approximation.)

Here is the markup for the PART_Track: 

       <Border Name="PART_Track" BorderBrush="DarkGray"

               BorderThickness="2" CornerRadius="3" >

          <Rectangle Fill="LightGray"

               RadiusX="3" RadiusY="3"/>

        </Border> 

Note that to achieve the rounded corner effect, Border uses CornerRadius and Rectangle uses RadiusX and RadiusY.

The markup for the Indicator is just as simple: 

          <Rectangle Name="PART_Indicator" HorizontalAlignment="Right"

                    RadiusX="3" RadiusY="3" 

                    Margin="2,2,2,2">

          <Rectangle.Fill>

            <LinearGradientBrush EndPoint="0.5,0" StartPoint="0.5,1">

              <GradientStop Color="#FF5FBB23" Offset="0.525"/>

              <GradientStop Color="White"/>

              <GradientStop Color="#FF63BC28"/>

              <GradientStop Color="#FF95EDA5" Offset="0.542"/>

            </LinearGradientBrush>

          </Rectangle.Fill>

        </Rectangle> 

The key property here is the HorizontalAlignment. By setting it to Right we ensure that the ProgressBar Indicator will start at the right hand side and stretch out towards the left.

Demo
The following markup in a Window will create two ProgressBars, one default style and the other using this ControlTemplate: 

    <Grid>

    <Grid.RowDefinitions>

      <RowDefinition Height="87*" />

      <RowDefinition Height="175*" />

    </Grid.RowDefinitions>

 

    <ProgressBar x:Name="PBDefault" Width="150" Height="33">

 

    </ProgressBar>

    <ProgressBar Name="MyPB" Width="150" Height="33"

          Template="{StaticResource ReverseBasicPB}" Margin="22"

          Grid.Row="1"

          VerticalAlignment="Top" />

  </Grid> 

Add in the animation code-behind used in previous examples: 

    Private Sub ReverseBasic_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim a As New DoubleAnimation

        a.From = 0

        a.To = 100

        a.Duration = New TimeSpan(0, 0, 6)

 

        MyPB.BeginAnimation(ProgressBar.ValueProperty, a)

 

        a.From = 0

        a.To = 100

        a.Duration = New Duration(TimeSpan.Parse("0:0:6"))

 

        PBDefault.BeginAnimation(ProgressBar.ValueProperty, a)

    End Sub 

When you run this, completed ProgressBars are reasonably close in looks, the only major difference being that the templated one at the bottom runs right to left.

 

Although the creation of the ControlTemplate was only a few minutes work and opens the way for further visual changes (which I'll probably blog about later), if you just want the default ProgressBar reversed, the simple way is as follows:

Add a new ProgressBar to the Window, but this time include a LayoutTransform to rotate it 180 degrees. 

    <ProgressBar x:Name="PBDefaultReversed"

                 Width="150" Height="33"

                 Grid.Row="2" >

      <ProgressBar.LayoutTransform>

        <RotateTransform Angle="180" />

      </ProgressBar.LayoutTransform>

    </ProgressBar> 

(You'll also need to add the extra row to the Grid if you are following along and creating this sample yourself:) 

    <Grid.RowDefinitions>

      <RowDefinition Height="87*" />

      <RowDefinition Height="85*" />

      <RowDefinition Height="90*" />

    </Grid.RowDefinitions> 

And don't forget to animate it in the code-behind.

The final result, with the default ProgressBar at the top, the templated one in the middle and the transformed one at the bottom, looks like this:

I chose not to use a gradient for the background Gray, but of course you can implement this if you prefer.

WPF: Changing ProgressBar Appearance As Values Change

 

Introduction
In this earlier blog, I looked at how to create a non-rectangular ProgressBar. As the next step, I want to look at how you can change properties dynamically as the current Value of the ProgressBar changes.

I'll start with the easy (and fairly realistic) scenario where you want to tone down the ProgressBar once it has reached its maximum value.

Currently the ProgressBar coloring looks like this:

If you want to apply changes to the look of the ProgressBar as it is running, you can use a DataTrigger in XAML or you can simply use the ValueChanged event of the ProgressBar in the code-behind. I say "simply", but you do in fact have to understand how to dig into the ControlTemplate and drill down in order to make the code-behind approach work.

There's a trade-off between these two approaches – DataTrigger or code-behind. DataTrigger offers a slightly more concise syntax, but only as long as your requirements are very basic. Once you step beyond the absolute basics, the XAML becomes quite complex. And as you are no doubt more familiar with VB, what's the point in struggling just to prove the point that it can be created in XAML? Sometimes, common sense has to win over technical ego.

Overall, in most cases, code-behind is the easiest answer. But just to be sure you know what a DataTrigger is and how it works, let's run through it.

DataTrigger
A DataTrigger is fired (and as a result some predefined change happens) when a value is reached. In the scenario we are looking at here, a DataTrigger could be set to fire when the Value property of a ProgressBar reaches its maximum. So in the case of the ProgressBar defined below that would of course be when it reaches 100.  

    <ProgressBar x:Name="CurvyPB" Width="300" Height="60"

       Template="{StaticResource PBCurvy}"

       Foreground="{StaticResource BlueGreenRed}"

       Minimum="0" Maximum="100" /> 

The 'predefined change' might be to alter the color of the outside edge of the control and also to dim its Opacity, so that it can be more easily ignored now that its work is done.

The DataTrigger sits in a Triggers collection in the ControlTemplate for the ProgressBar. The syntax is as follows: 

      <ControlTemplate.Triggers>

        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Value}"

                    Value="100">

          <Setter TargetName="PART_Track" Property="Stroke" Value="DarkGray" />

          <Setter TargetName="PART_Track" Property="Opacity" Value="0.3" />

        </DataTrigger>

      </ControlTemplate.Triggers> 

(Note that this is only one section of the complete ControlTemplate.)

The DataTrigger Binding looks awkward and is one of those rather peculiar constructs that makes XAML hard to decipher (and create) sometimes. Essentially it translates to :
"Keep checking the Value property of the ProgressBar instance that is using this ControlTemplate. If and when it reaches a Value of 100, fire the trigger."

The Setters both target the Path named 'PART_Track'. The first changes the Stroke to DarkGray and the second one turns down the Opacity of that Path (which effectively reduces the Opacity of the templated ProgressBar).

Here is the full XAML for the Window which contains the ControlTemplate and the ProgressBar instance: 

<Window x:Class="CurvyWithTriggers"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:converter="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Luna"  

   Title="Curved ProgressBar" Height="200" Width="400">

  <Window.Resources>

    <ControlTemplate x:Key="PBCurvy" TargetType="{x:Type ProgressBar}">

      <Grid>

        <Path x:Name="PART_Track"

             Stroke="{StaticResource BlueGreenRed}"

             StrokeThickness="5"

             Data="F1 M46.802502,0.50000018 C59.803562,0.50000006 71.553123,3.7052743 79.942001,

              8.9014616 C88.330879,3.7052746 100.08044,0.5 113.0815,0.50000018 C125.92575,

              0.5 137.54851,3.6284194 145.9305,8.6908474 C154.3125,3.6284194 165.93524,

              0.50000006 178.7795,0.50000018 C204.35167,0.5 225.082,12.900593 225.082,

              28.1975 C225.082,43.494408 204.35167,55.895 178.7795,55.895 C165.93524,

              55.895 154.3125,52.766582 145.9305,47.704151 C137.54851,52.766582 125.92575,

              55.895 113.0815,55.895 C100.08044,55.895 88.330879,52.689728 79.942001,

              47.493538 C71.553123,52.689728 59.803562,55.895 46.802502,55.895 21.230335,

              55.895 0.5,43.494408 0.5,28.1975 0.5,12.900593 21.230335,0.5 46.802502,

              0.50000018 z"

             Stretch="Fill">

          <Path.Fill>

            <MultiBinding>

              <MultiBinding.Converter>

                <converter:ProgressBarBrushConverter />

 

              </MultiBinding.Converter>

              <Binding Path="Foreground" RelativeSource="{RelativeSource TemplatedParent}" />

              <Binding Path="IsIndeterminate" RelativeSource="{RelativeSource TemplatedParent}" />

              <Binding Path="ActualWidth" ElementName="PART_Indicator" />

              <Binding Path="ActualHeight" ElementName="PART_Indicator" />

              <Binding Path="ActualWidth" ElementName="PART_Track" />

 

            </MultiBinding>

          </Path.Fill>

        </Path>

        <Decorator x:Name="PART_Indicator" />

       </Grid>

 

      <ControlTemplate.Triggers>

        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Value}"

                    Value="100">

          <Setter TargetName="PART_Track" Property="Stroke" Value="DarkGray" />

          <Setter TargetName="PART_Track" Property="Opacity" Value="0.3" />

        </DataTrigger>

      </ControlTemplate.Triggers>

    </ControlTemplate>

 

  </Window.Resources>

  <Grid>

    <ProgressBar x:Name="CurvyPB" Width="300" Height="60"

       Template="{StaticResource PBCurvy}"

       Foreground="{StaticResource BlueGreenRed}"

       Minimum="0" Maximum="100" />

 

  </Grid>

</Window> 

This markup in the Application.xaml file creates the LinearGradientBrush: 

    <Application.Resources>

    <LinearGradientBrush x:Key="BlueGreenRed"

       EndPoint="1,0.5" StartPoint="0,0.5">

      <GradientStop Color="#FF2D3ADD" Offset="0"/>

      <GradientStop Color="#FFF13E14" Offset="1"/>

      <GradientStop Color="#FF9775D8" Offset="0.192"/>

      <GradientStop Color="#FF3F893B" Offset="0.481"/>

      <GradientStop Color="#FF2B9518" Offset="0.625"/>

      <GradientStop Color="#FECC7638" Offset="0.812"/>

    </LinearGradientBrush>

  </Application.Resources> 

The code-behind to animate the ProgressBar: 

Imports System.Windows.Media.Animation

 

Partial Public Class CurvyWithTriggers

 

    Private Sub CurvyWithTriggers_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim a As New DoubleAnimation

        a.From = 0

        a.To = 100

        a.Duration = New TimeSpan(0, 0, 8)

 

        CurvyPB.BeginAnimation(ProgressBar.ValueProperty, a)

    End Sub

End Class 

Apologies if you already have all that code and markup from the previous blog. Personally I hate it when someone says "I've used the same code as in my earlier blog", which means I then have to go and find that blog, dig through it and find the missing bits before I can test out what I'm working on now. So I much prefer to repeat it and make life easier for you, at the expense of a slightly longer blog entry.

After the ProgressBar completes its mission, it hits that Value of 100 and the look changes to this:

So if you're sat there saying to yourself that there's nothing there that you couldn't have easily done in code-behind, I almost agree with you. Certainly the Opacity change would be easy: 

        If progressBar1.Value = 100 Then

            progressBar1.Opacity = 0.3

        End If 

But what about changing the value of the Stroke property though? The ProgressBar doesn't have a Stroke property. That's tucked away inside the ControlTemplate and is a property of the Path named PART_Track. To get to that, we will need a way to access the ControlTemplate and then drill down into the Path.

Before we look at the code-behind approach, I just want to mention a problem with DataTriggers, especially as they relate to ProgressBars. The only arithmetic operator available to you in the DataTrigger in XAML is the equals operator. For fairly obvious reasons, the less-than and greater-then operator symbols have the potential to cause problems in a language that uses them as element delimiters.

The reason that this is a particular problem with the ProgressBar is that the algorithm that breaks the ProgressBar movement into its time chunks will rarely, if ever, space them across whole numbers. That is to say if the Minimum Value is 0 and the Maximum Value is 100 and the duration is 10 seconds, you might reasonably suppose that each new block will appear at intervals of 0, 10, 20, 30, etc. However it doesn't work that way and the values are more likely to be something like :
0
0.252538
0.299015
0.613981
: etc
: ending with
99.769844
99.890324
100

This makes it impossible for you to set a DataTrigger on a value that you can be certain will be matched exactly, except for the starting and ending values of 0 and 100. In theory, you could tweak the size settings of the PART_Track and PART_Indicator to ensure whole number partitions (these being the key factors in the breakdown), but none of my experimenting with this approach worked. An alternative approach, which I haven't tried, would be to create a ValueConverter which would then allow you to set value parameters, such as < 20 or > 40 and so on. However, I didn't really see any gain in going that route, as I can use the ValueChanged event of the ProgressBar directly, together with a bit of WPF delving, as we will now see.

FindResource and FindName
When you need to get at a ControlTemplate (or other Resource, for that matter), you can use the FindResource method of the FrameworkElement class. Before, I get into that, I am going to make two changes to the markup in the Window.
The first change is to add a Name property to the Window, placing this inside the opening tag of the Window class markup:  

 x:Name="CurvyValueChangedWindow" 

This is necessary in order for the Window to be accessed from the code-behind.

The second change is purely decorative – changing the Stroke property of the PART_Track path from the LinearGradientBrush to plain Yellow.  

        <Path x:Name="PART_Track"

             Stroke="Yellow" 

 The ProgressBar now looks like this at startup:

To locate that ControlTemplate, I use the following code, which I have placed in the ValueChanged event of the ProgressBar: 

        Dim ct As New ControlTemplate()

        Try

            ct = CType(CurvyValueChangedWindow.FindResource("PBCurvy"), ControlTemplate)

 

        Catch Ex As ResourceReferenceKeyNotFoundException

            '  Design time message

            Console.WriteLine("CT Not found")

            '  Quit gracefully if not found

            Exit Sub

        End Try 

The third line does all the work and you will see now why I added a name to the WPF Window. This Name is used to identify the container in which the Resource named PBCurvy (i.e. the ControlTemplate) should be found.
Note also that if due to a typo or other error the Resource can't be found, then no further action will be taken. At this stage, the Catch is superfluous. It is however important to avoid the application crashing as we move on to the next step.

FindName
So now we have got ourselves a reference to the ControlTemplate, but we still need to drill down into the Path named PART_Track which is a sub-element of that template. To do this, you can use the following code, placing it immediately below the code used to find the ControlTemplate resource:  

        Dim p As New Path

        p = CType(ct.FindName("PART_Track", PBCurvy), Path)

 

        If IsNothing(p) Then

            Console.WriteLine("Path Not found")

            Exit Sub

        End If 

This time the FindName function searches through that ControlTemplate (now referenced as 'ct') in order to find the named Path.
Once again, the test for IsNothing isn't necessary at this stage, because we have not yet tried to do anything with the Path variable 'p'. As before though, when we add further code, this checkpoint – and the Exit Sub if the Path isn't found – are important.

Changing The values
We've now reached the point where we can manipulate values of that Path according to the current values of the ProgressBar. This code placed immediately below the previous snippet will change the color and thickness of the Stroke: 

        Select Case PBCurvy.Value

            Case Is < 25

                p.Stroke = New SolidColorBrush(Colors.Yellow)

                p.StrokeThickness = 5

            Case 25 To 49

                p.Stroke = New SolidColorBrush(Colors.Green)

                p.StrokeThickness = 7

            Case 50 To 75

                p.Stroke = New SolidColorBrush(Colors.Orange)

                p.StrokeThickness = 9

            Case 76 To 99

                p.Stroke = New SolidColorBrush(Colors.Red)

                p.StrokeThickness = 11

            Case Else

                p.Stroke = New SolidColorBrush(Colors.DarkGray)

        End Select 

When you run the application, the colors will change as things progress:  

 

 I have chosen to make those particular changes for demo purposes, but you are of course not limited to those. The key take away points in this blog are the availability of the DataTrigger and the use of the very helpful FindResource and FindName methods when you want to trigger a change via the code-behind.

WPF: Displaying a Master-Detail Collection of In-Memory Objects In a TreeView

 Introduction
In this earlier blog I used an XML file containing nested data items as the data source of a TreeView. In this version I will use a collection of objects as the data source.
The details of the data are much the same as I used in the previous blog, with a couple of tiny changes to avoid any possible confusion caused by the use of Visual Basic keywords as field names:

These classes are saved in a VB file named SalesData.vb. Because it is fairly lengthy, filling about three screen display lengths, I've made it available from this link. (To a large extent, how the the data is created isn't really the topic of this blog – I'm more concerned with the Binding to a source and the templates used for displaying the data.)

Mapping to the local Assembly
In order to be able to access those SalesPerson, SalesOrder, etc classes and objects in the XAML file, it is necessary to create a mapping. The syntax for this is as follows:  

  xmlns:local="clr-namespace:HierarchicalDataTemplate" 

It isn't mandatory to use 'local' as the mapping alias, but it's a fairly traditional approach. In this case, of course 'HierarchicalDataTemplate' is the name of the project I am working on.

With the namespace mapping in place, any of the classes that currently exist in the code-behind files of the project become visible to the XAML file. If you view the code listing for the SalesData.vb file, you will see that the class which creates the demo data is called 'SalesPersonList'. It's now possible to create a new instance of that class in the XAML file : 

    <local:SalesPersonList x:Key="SalesPersonList"/> 

I generally place this in the Window.Resources collection.

Creating a TreeView and Binding its Data Source
In the markup for the Window itself, I'll create a TreeView which contains a single TreeViewItem.  

    <TreeView>

      <TreeViewItem ItemsSource="{Binding Source={StaticResource SalesPersonList}}"

          Header="Sales Figures" />

    </TreeView> 

The ItemsSource is the crucial property here. It identifies exactly where the TreeView should look for its data. In this case it looks into the SalesPersonList instance that I created a few moments ago. For the avoidance of doubt, the exact 'SalesPersonList' that is used is the StaticResource created earlier and identified by the Key. (I possibly should have used a different name for the Key and the underlying Class to avoid any confusion).

Because of the use of the Binding and the HierarchicalDataTemplates, it is only necessary to create this single TreeViewItem. The Binding engine will trawl through all the data and create as many TreeViewItem nodes as it needs to in order to display everything correctly. 

HierarchicalDataTemplates
This example uses three HierarchicalDataTemplates, plus a standard DataTemplate for the SalesItems. Here is the first one (which again I've placed in the Window.Resources collection):  

    <HierarchicalDataTemplate DataType="{x:Type local:SalesPerson}"

          ItemsSource="{Binding Path=Periods}">

      <TextBlock Text="{Binding Path=Name}"/>

    </HierarchicalDataTemplate> 

The DataType property identifies which type of object will be dealt with by this template. In the case of this first template, this will be the SalesPerson type. In other words, when the Binding that I placed on the TreeViewItem finds any instance of a SalesPerson inside that SalesPersonList, it will look at this template to discover how it should display SalesPerson details.
In this case, the Name property of the current SalesPerson will be shown in a TextBlock.
The ItemsSource property also uses a Binding, but it is only interested in knowing what needs to be shown as the child data of SalesPersons. As we know from the diagram above, this will be the Periods data.

If you are finding the DataType and the Bindings' Paths a bit tricky to grasp, it may help if you run the project as it currently stands. Here's the markup for the Window so far: 

<Window x:Class="OrdersListsDisplay"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:local="clr-namespace:HierarchicalDataTemplate"

   Title="Sales List Display" Height="300" Width="300">

  <Window.Resources>

    <local:SalesPersonList x:Key="SalesPersonList"/>

 

    <!–  Data Templates –>

    <HierarchicalDataTemplate DataType="{x:Type local:SalesPerson}"

          ItemsSource="{Binding Path=Periods}">

      <TextBlock Text="{Binding Path=Name}"/>

    </HierarchicalDataTemplate>

 

  </Window.Resources>

    <Grid>

    <TreeView>

      <TreeViewItem ItemsSource="{Binding Source={StaticResource SalesPersonList}}"

          Header="Sales Figures" />

    </TreeView>

  </Grid>

</Window> 

When you run this, you will first see:

Then, when you expand the first node, you will have:

Finally, clicking on either or both the SalesPerson nodes, you will see that the application has tried to display the children of the SalesPersons for you. In the absence of any instruction about formatting the Periods, it simply reverts to displaying the default ToString rendering of the class.

Hopefully though you can now see why the template points to SalesPerson as its DataType, but identifies the next level down the tree as the ItemsSource. (You may have noticed that this wasn't necessary with the XML data example in the earlier blog, where simply assigning a Binding without a Path will work.)

Displaying Periods and SalesOrders
Exactly the same approach is used for the next two templates: 

    <HierarchicalDataTemplate DataType="{x:Type local:Period}"

          ItemsSource="{Binding Path=SalesOrders}">

      <TextBlock Text="{Binding Path=Name}"/>

    </HierarchicalDataTemplate>

 

    <HierarchicalDataTemplate DataType="{x:Type local:SalesOrder}"

         ItemsSource="{Binding Path=SalesItems}">

      <TextBlock Text="{Binding Path=Name}"/>

    </HierarchicalDataTemplate> 

The three HierarchicalDataTemplates in place so far will produce this result:

which should come as no surprise, based on what I explained earlier.

ItemDetail DataTemplate
So that just leaves the template for the SalesItems and ItemDetail data. This time a standard DataTemplate will do, because we know there is no further data below ItemDetail.  

    <DataTemplate DataType="{x:Type local:SalesItem}">

      <TextBlock Text="{Binding Path=ItemDetail}"/>

    </DataTemplate> 

Just for the record, a HierarchicalDataTemplate would work but isn't necessary. With the final DataTemplate added, every level of the Master-Detail data can be accessed:

Formatting
You have many options for formatting the final presentation of the data. In this example, you can change the color, font size and weight, indentation, etc via the individual TextBlock properties. If you wanted to insert additional graphical detail (such as an icon), you simply wrap the TextBlock in a StackPanel with its Orientation set to Horizontal. You can then insert the icon image as an additional child of this StackPanel.

You can build on this basic example shown here to create a much more complex display.

Creating something from nothing, asynchronously [Developer-friendly virtual file implementation for .NET improved!]

Last week I posted the code for VirtualFileDataObject, an easy-to-use implementation of virtual files for .NET and WPF. This code implements the standard IDataObject COM interface for drag-and-drop and clipboard operations and is specifically targeted at scenarios where an application wants to allow the user to drag an element to a folder and create a file (or files) dynamically on the drop/paste. The standard .NET APIs for drag-and-drop don’t support this scenario, so VirtualFileDataObject ended up being a custom implementation of the System.Runtime.InteropServices.ComTypes.IDataObject interface. Fortunately, the specifics aren’t too difficult, and a series of posts by Raymond Chen paved the way (in native code).

VirtualFileDataObjectDemo sample application

 

If you read my previous post, you may recall there was an issue with the last scenario of the sample: the application became unresponsive while data for the virtual file was downloading from the web. While this unresponsiveness won’t be a noticeable for scenarios involving local data, scenarios that create large files or hit the network are at risk. Well, it’s time to find a solution!

And we don’t have to look far: the answer is found in the MSDN documentation for Transferring Shell Objects with Drag-and-Drop and the Clipboard under the heading Using IAsyncOperation. As you might expect, we’re not the first to notice this behavior; the IAsyncOperation interface exists to solve this very problem. So it seems like things ought to be easy – let’s just define the interface, implement its five methods (none of which are very complicated), and watch as the sample application stays responsive during the time-consuming download…

FAIL.

 

Okay, so that didn’t work out quite how we wanted it to. Maybe we defined the interface incorrectly? Maybe we implemented it incorrectly? Or maybe Windows just doesn’t support this scenario??

No, no, and no. We've done everything right – it's the platform that has betrayed us. :( Specifically, the DragDrop.DoDragDrop method does something sneaky under the covers: it wraps our respectable System.Runtime.InteropServices.ComTypes.IDataObject instance in a System.Windows.DataObject wrapper. Because this wrapper object doesn’t implement or forward IAsyncOperation, it’s as if the interface doesn’t exist!

Aside: I have the fortune of working with some of the people who wrote this code in the first place, and I asked why this extra level of indirection was necessary. The answer is that it probably isn’t – or at least nobody remembers why it’s there or why it couldn’t be removed now. So the good news is they’ll be looking at changing this behavior in a future release of WPF. The bad news is that the change probably won’t happen in time for the upcoming WPF 4 release.

Be that as it may, it looks like we’re going to need to call the COM DoDragDrop function directly. Fortunately, there’s not much that happens between WPF’s DragDrop.DoDragDrop and COM’s DoDragDrop, so there’s not much we have to duplicate. That said, we do need to define the IDropSource interface and write a custom DropSource implementation of its two methods. The nice thing is that both methods are pretty simple and straightforward, so our custom implementation can be private. (And for simplicity’s sake, we’re not going to bother raising DragDrop's (Preview)GiveFeedbackEvent or (Preview)QueryContinueDragEvent events.)

Because we’ve been careful to define the VirtualFileDataObject.DoDragDrop replacement method with the same signature as the DoDragDrop method it’s replacing, updating the sample to use it is trivial. So run the sample again, and – BAM – no more unresponsive window during the transfer! (For real, this time.) You can switch to the window, resize it, drag it, etc. all during the creation of the virtual file.

 

But now we’ve got a bit of a dilemma: if things are happening asynchronously, how can we tell when they’re done? The answer lies with the StartOperation and EndOperation methods of the IAsyncOperation interface. Per the interface contact, these methods are called at the beginning/end of the asynchronous operation. So if we just add another constructor to the VirtualFileDataObject class, we can wire things up in the obvious manner:

 /// <summary>  /// Initializes a new instance of the VirtualFileDataObject class.  /// </summary>  /// <param name="startAction">Optional action to run at the start of the data transfer.</param>  /// <param name="endAction">Optional action to run at the end of the data transfer.</param>  public VirtualFileDataObject(Action startAction, Action endAction) 

Well, almost… The catch is that while VirtualFileDataObject now supports asynchronous mode, there’s no guarantee that the drop target will use it. Additionally, the developer may have specifically set the VirtualFileDataObject.IsAsynchronous property to false to disable asynchronous mode. And when you’re in synchronous mode, there aren’t any handy begin/end notifications to rely on…

So I’ve added support to VirtualFileDataObject for calling the begin/end actions in synchronous mode based on some semi-educated guesses. In my testing, the notifications during synchronous mode behave as identically as possible to those in asynchronous mode. Granted, in some scenarios startAction may run a little earlier in synchronous mode than it would have for asynchronous mode – but as far as the typical developer is concerned, VirtualFileDataObject offers the same handy behavior for both modes!

 

Let’s celebrate by updating the sample to show a simple busy indicator during the download:

VirtualFileDataObjectDemo sample application

Code-wise, once we’ve updated the call to DoDragDrop:

VirtualFileDataObject.DoDragDrop(dragSource, virtualFileDataObject, DragDropEffects.Copy);

Everything else stays the same except for the constructor:

var virtualFileDataObject = new VirtualFileDataObject(     // BeginInvoke ensures UI operations happen on the right thread     () => Dispatcher.BeginInvoke((Action)(() => BusyScreen.Visibility = Visibility.Visible)),     () => Dispatcher.BeginInvoke((Action)(() => BusyScreen.Visibility = Visibility.Collapsed))); 

The BusyScreen variable above corresponds to a new element in the sample application that provides the simple “Busy…” UI shown above. In real life, we’d probably use MVVM and a bool IsBusy property on our data model to trigger this, but for a sample application, toggling Visibility works fine. Because all the hard work is done by the VirtualFileDataObject class [you're welcome! :) ], the application remains unencumbered by complex logic for anything related to the management of virtual files.

Which is the way it should be!

 

[Click here to download the complete code for VirtualFileDataObject and the sample application.]

 

PS – I have one more post planned on this topic demonstrating something I haven’t touched on yet to help applications coordinate better with the shell. Stay tuned…

WP Like Button Plugin by Free WordPress Templates