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.

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 ListView – Formatting Rows

 By the end of the previous blog item on the topic of the WPF ListView, the ListView looked like this:

In this blog I want to look at ways of changing the look of the rows. You may, for example, want to assign a background color to the rows. And based on what we have done so far, you might be tempted to edit the DataTemplates used for the cells. Maybe you would think about adding a Border with a Background fill and placing this around the TextBlock for each cell:  

  <DataTemplate x:Key="IDCellTemplate2">

      <Border Background="LightGreen">

      <TextBlock Foreground="MediumBlue"

                FontFamily="Calibri"

         Text="{Binding Path=ProductID}" />

      </Border>

    </DataTemplate> 

I won't waste the space showing the markup for all three templates here, but if you did try this you will get a disappointing result:

Pretty ugly, I think you'll agree. Playing around with the Margin or Padding won't get you any further either.

The answer is to use the ItemContainerStyle property of the ListView. This opens up a number of choices for you. Firstly, if you would like to still have a gap between each of the columns – but an even gap – then you can take the following approach:  

      <ListView.ItemContainerStyle>

        <Style TargetType="ListViewItem">

          <Setter Property="HorizontalContentAlignment" Value="Stretch" />

        </Style>

      </ListView.ItemContainerStyle> 

This will produce the following output, which is an improvement:

If you want the light green to spread across the whole row, then you still use the ItemContainerStyle, but this time you set the Background property of the ListViewItem:  

      <ListView.ItemContainerStyle>

        <Style TargetType="ListViewItem">

            <Setter Property="Background" Value="LightGreen" />

        </Style>

      </ListView.ItemContainerStyle> 

Now you get the result you are probably looking for.

 

If all that whiteness in the background is a bit too much for you, then of course you can blend another shade of green into the mix by setting the ListView's Background property. Using SeaGreen, for example, will produce this:

 

 

The final markup for the ListView looks like this:  

    <ListView Name="ProductsListView"

         ItemsSource="{Binding}"

           Margin="5,25"

              Background="SeaGreen">

      <ListView.ItemContainerStyle>

        <Style TargetType="ListViewItem">

            <Setter Property="Background" Value="LightGreen" />

        </Style>

      </ListView.ItemContainerStyle>

 

      <ListView.View>

        <GridView>

          <!– Product ID –>

          <GridViewColumn

           HeaderTemplate="{StaticResource IDColHeader}"

          CellTemplate="{StaticResource IDCellTemplate}">

          </GridViewColumn>

          <!– Product Name –>

          <GridViewColumn

           HeaderTemplate="{StaticResource NameColHeader}"

           CellTemplate="{StaticResource NameCellTemplate}">

          </GridViewColumn>

          <!– Pack Size –>

          <GridViewColumn

           HeaderTemplate="{StaticResource PackageColHeader}"

           CellTemplate="{StaticResource PackCellTemplate}">

          </GridViewColumn>

        </GridView>

      </ListView.View>

    </ListView> 

The markup for the DataTemplates and Styles is the same as that shown in the previous blog, all of which I placed in the Application.xaml file (App.xaml for C# projects).

There are some other row formatting options I want to cover, but I will leave those for another day.

WP Like Button Plugin by Free WordPress Templates