Archive for the ‘XAML’ Category.

WPF Binding to a property of a static class

Binding to a property of a static class is quite straight forward.

The binding string looks like this:

{x:Static s:MyStaticClass.StaticValue2}

For an example do this:

  1. Create a new folder in your project called Statics.
  2. Add the following class to the Statics folder.
    using System;
    
    namespace BindingToStaticClassExample.Statics
    {
        public static class MyStaticClass
        {
            static MyStaticClass()
            {
                Title = "Binding to properties of Static Classes";
                StaticValue1 = "Test 1";
                StaticValue2 = "Test 2";
                StaticValue3 = "Test 3";
            }
    
            public static String Title { get; set; }
            public static String StaticValue1 { get; set; }
            public static String StaticValue2 { get; set; }
            public static String StaticValue3 { get; set; }
        }
    }
    
  3. Add a reference to the BindingToStaticClassExample.Statics namespace. (See line 4.)
  4. Bind the title to the Title string value of MyStaticClass.
  5. Change the <Grid> to a <StackPanel> (not required just for ease of this example).
  6. Add TextBox elements and bind them to the two string values in MyStaticClass object.
    <Window x:Class="BindingToStaticClassExample.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:s="clr-namespace:BindingToStaticClassExample.Statics"
            Title="{x:Static s:MyStaticClass.Title}" Height="350" Width="525">
        <StackPanel>
            <TextBox Text="{x:Static s:MyStaticClass.StaticValue1}" />
            <TextBox Text="{x:Static s:MyStaticClass.StaticValue2}" />
            <!-- Not supported and won't work
            <TextBox>
                <TextBox.Text>
                    <Binding Source="{x:Static s:MyStaticClass.StaticValue3}" />
                </TextBox.Text>
            </TextBox>
            -->
        </StackPanel>
    </Window>
    

You now know how to bind to properties of a static class.

TextBox Validation – How to bind to properties of the Validation of a TextBox?

Have you ever wanted to have a TextBox that requires data or specifically formatted data and you want to enforce this validation and display this to the user.

Well, I figured this out, with a great deal of pain, but hey, it works.

You can download this project in it’s entirety here:

WpfTextBoxValidation.zip

I started with a new WPF Project in Visual Studio. Here is a general overview of the steps I took to make this example project happen.

MainWindow.xaml

  1. Here, create a simple form to fill out using TextBlock and TextBox pairs in a Grid.
  2. Configure the TextBox elements to use binding.
  3. Configure the TextBox elements to use the appropriate Validation. (See the validation examples we will create below.)
  4. Below the form you created in a StackPanel, add some TextBlock elements to display the Validation’s ErrorConent.
  5. Bind each TextBlock element to the TextBox in the form they pertain to.
  6. Bind the Visibility of each TextBlock to the Errors. (We will use the converter below to return Visible if there is ErrorContent and Collapsed if not.)
<Window x:Class="WpfTextBoxValidation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfTextBoxValidation"
        Title="MainWindow" Height="350" Width="525">
    <Grid Name="MainGrid">
        <Grid.Resources>
            <Style TargetType="TextBox">
                <Setter Property="MaxWidth" Value="200" />
            </Style>
        </Grid.Resources>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" MinWidth="200" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <!-- Labels -->
        <TextBlock Text="FirstName" />
        <TextBlock Text="LastName" Grid.Row="1" />
        <TextBlock Text="Age" Grid.Row="2" />
        <TextBlock Text="Phone" Grid.Row="3" />

        <!-- TextBlocks -->
        <TextBox Name="TextBoxFirstName" Grid.Column="1">
            <TextBox.Text>
                <Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged" >
                    <Binding.ValidationRules>
                        <local:TextBoxNotEmptyValidationRule x:Name="FirstNameValidation" ValidatesOnTargetUpdated="True" 
                                                             Message="You must enter a first name."/>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>
        <TextBox Name="TextBoxLastName" Grid.Row="1" Grid.Column="1">
            <TextBox.Text>
                <Binding Path="LastName" UpdateSourceTrigger="PropertyChanged" >
                    <Binding.ValidationRules>
                        <local:TextBoxNotEmptyValidationRule x:Name="LastNameValidation" ValidatesOnTargetUpdated="True" 
                                                             Message="You must enter a last name."/>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>
        <TextBox Name="TextBoxAge" Grid.Row="2" Grid.Column="1">
            <TextBox.Text>
                <Binding Path="Age" UpdateSourceTrigger="PropertyChanged" >
                    <Binding.ValidationRules>
                        <local:OverThirteenValidationRule x:Name="AgeValidation" ValidatesOnTargetUpdated="True"/>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>
        <TextBox Name="TextBoxPhone" Grid.Row="3" Grid.Column="1">
            <TextBox.Text>
                <Binding Path="Phone" UpdateSourceTrigger="PropertyChanged" >
                    <Binding.ValidationRules>
                        <local:TextBoxNotEmptyValidationRule x:Name="PhoneValidation" ValidatesOnTargetUpdated="True" 
                                                             Message="You must enter a phone number."/>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>

        <!-- Validation List -->
        <StackPanel Grid.Row="4" Grid.ColumnSpan="2">
            <StackPanel.Resources>
                <Style TargetType="TextBlock">
                    <Setter Property="Foreground" Value="Red" />
                </Style>
                <local:ErrorCollectionToVisibility x:Key="ToVisibility" />
            </StackPanel.Resources>
            <TextBlock Visibility="{Binding ElementName=TextBoxFirstName, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}">
                <TextBlock.Text>
                    <MultiBinding StringFormat="FirstName - {0}">
                        <Binding ElementName="TextBoxFirstName" Path="(Validation.Errors)[0].ErrorContent"/>
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>
            <TextBlock Visibility="{Binding ElementName=TextBoxLastName, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}">>
                <TextBlock.Text>
                    <MultiBinding StringFormat="LastName - {0}">
                        <Binding ElementName="TextBoxLastName" Path="(Validation.Errors)[0].ErrorContent"/>
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>
            <TextBlock Visibility="{Binding ElementName=TextBoxAge, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}">>
                <TextBlock.Text>
                    <MultiBinding StringFormat="Age - {0}">
                        <Binding ElementName="TextBoxAge" Path="(Validation.Errors)[0].ErrorContent"/>
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>
            <TextBlock Visibility="{Binding ElementName=TextBoxPhone, Path=(Validation.Errors), Converter={StaticResource ToVisibility}}">>
                <TextBlock.Text>
                    <MultiBinding StringFormat="Phone - {0}">
                        <Binding ElementName="TextBoxPhone" Path="(Validation.Errors)[0].ErrorContent"/>
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>
            <!--Text="{Binding ElementName=DirectoryBox, Path=(Validation.Errors)[0].ErrorContent}"-->
        </StackPanel>
    </Grid>
</Window>

MainWindow.xaml.cs

To this file we add a Person class and then create and instance of it and set it the instance as the DataContext.

using System;
using System.Windows;
using System.ComponentModel;

namespace WpfTextBoxValidation
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Person p = new Person();
            MainGrid.DataContext = p;
        }
    }

    public class Person
    {
        public String FirstName { get; set; }
        public String LastName { get; set; }
        public int Age { get; set; }
        public String Phone { get; set; }
    }
}

ErrorCollectionToVisibility.cs

This class is used to convert the Validation.Error collection to a Visibility.

There is a property Validation.HasError but for some reason it is not available to bind to. If it were, I would bind to it and use the built-in BooleanToVisibility converter. But since, I can’t, I used this ErrorCollectionToVisibility converter which simply returns Visible if there is at least one item in the collection or Collapse if the Collection is null or empty.

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfTextBoxValidation
{
    class ErrorCollectionToVisibility : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            ReadOnlyCollection<ValidationError> collection = value as ReadOnlyCollection<ValidationError>;
            if (collection != null && collection.Count > 0)
                return Visibility.Visible;
            else
                return Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return new object();
        }
    }
}

OverThirteenValidationRule.cs

Here we check if the value is greater than 13 and if not, we return the false ValidationResult.

using System;
using System.Windows.Controls;

namespace WpfTextBoxValidation
{
    public class OverThirteenValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            if (value != null)
            {
                int age = 0;
                try
                {
                    age = Convert.ToInt32(value);
                }
                catch
                {
                    return new ValidationResult(false, "You must be older than 13!");
                }

                if (age > 13)
                    return ValidationResult.ValidResult;

            }
            return new ValidationResult(false, "You must be older than 13!");
        }
    }
}

TextBoxNotEmptyValidationRule.cs

This validation just makes sure there is at least one character in a TextBlock.

using System;
using System.Windows.Controls;

namespace WpfTextBoxValidation
{
    public class TextBoxNotEmptyValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            string str = value as string;
            if (str != null)
            {
                if (str.Length > 0)
                    return ValidationResult.ValidResult;
            }
            return new ValidationResult(false, Message);
        }

        public String Message { get; set; }
    }
}

You have now learned to bind to Validation.ErrorContent.

 

Loading rich text file links in a browser from a WPF Navigation Application

Previously, I discussed loading a rich text file in a regular WPF Application in this post:
Loading a RichTextBox from an RTF file using binding or a RichTextFile control

The following use cases must be met:

  1. Load and display a rich text file in read only mode
  2. The links must open inside a browser and not in the application

We created a RichTextFile control that inherits RichTextBox and configured it to support binding. Now we are going to use this same object in a WPF Navigation Application.

A WPF Navigation Application is going to react differently. Links are going work by default, so the Hyperlink.Click event doesn’t have to be used. However, there is a problem, the links open in the actual application’s window and not in a browser. Lets fix this.

Part 2 – Using the RichTextFile class in a WPF Navigation Application

Use Case 1 – Loading a rich text file

Create a new WPF Application in Visual Studio.

Add a Frame element and set the source to RTF.xaml, which we will create next.

<Window x:Class="RichTextFileTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Frame Source="RTF.xaml" />
    </Grid>
</Window>

There is no code behind for this, yet.

Create a new WPF Page called RTF.xaml.

Add the above RichTextFile object to the project.

Add an xmlns reference to our new object. Then add our new object. Notice in our new object that we set IsReadOnly=”True” but we also set IsDocumentEnabled=”True”. This allows for clicking a link even though the document is read only.

<Page x:Class="RichTextFileTest.RTF"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"
      Title="RTF"
      xmlns:controls="clr-namespace:System.Windows.Controls">
    <Grid>
        <controls:RichTextFile File="{Binding File}" IsReadOnly="True" IsDocumentEnabled="True"/>
    </Grid>
</Page>

Code Behind

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

namespace RichTextFileTest
{
    /// <summary>
    /// Interaction logic for RTF.xaml
    /// </summary>
    public partial class RTF : Page
    {
        public RTF()
        {
            InitializeComponent();
            Example example = new Example() { File = "File.rtf" };
            DataContext = example;
        }
    }

    public class Example
    {
        public String File { get; set; }
    }
}

Now the first use case is complete, the rich text file is loading into the RichTextFile control and is visible in the application. However, the second use case is incomplete, but not because the links aren’t loading, but because they are not loading in a browser. Instead they are loading inside the Frame element.

Use Case 2 – Getting the links to open in a browser

Getting the links to open in a browser is not straight forward and it took me quite some time to find an easy solution. Somehow, the link must be open in a browser and then the Navigation event must be canceled.

The easiest way to do this is to implement the Nagivating function on the Frame element in our MainWindow.

<Window x:Class="RichTextFileTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Frame Source="RTF.xaml" Navigating="Frame_Navigating"/>
    </Grid>
</Window>

Now implement the code behind for the Frame_Navigating method.

using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;

namespace RichTextFileTest
{
    /// <summary> 
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Frame_Navigating(object sender, System.Windows.Navigation.NavigatingCancelEventArgs e)
        {
            Frame frame = sender as Frame;
            if (frame != null && frame.Source != null)
            {
                // See if we are hitting a link using HTTP or HTTPS
                if (frame.Source.ToString().StartsWith("http://", System.StringComparison.CurrentCultureIgnoreCase)
                    || frame.Source.ToString().StartsWith("https://", System.StringComparison.CurrentCultureIgnoreCase))
                {
                    // Open the URL in a browser
                    Process.Start(frame.Source.ToString());

                    // Cancel the Navigation event
                    e.Cancel = true;
                }
            }
        }
    }
}

The links should now be opening in your browser and not in your application.

Here is the sample project that demonstrates this.
RichTextFileNavigation.zip

Loading a RichTextBox from an RTF file using binding or a RichTextFile control

Sometimes you have a rich text document that exists as an actual file and you simply want to load the file and display it.

My exact use cases are these:

  1. Load and display a rich text file in read only mode
  2. The links must open inside a browser and not in the application

I like to use MVVM, so my goal is to use binding to pass in the file name. Unfortunately the RichTextBox class is not designed to bind to a file name. However, extending this control to have this functionality is quite easy.

RichTextFile extending RichTextBox

Here is my new class. I have extended RichTextBox with four additional items in a new derived class called RichTextFile.

  1. Added a String Property called File.
  2. Added a DependecyProperty called FileProperty
  3. Added a PropertyChangedCallback function called OnFileChanged
  4. Added a ReadFile function that reads a .rtf file into a FlowDocument

RichTextFile.cs

using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

namespace System.Windows.Controls
{
    class RichTextFile : RichTextBox
    {
        #region Properties
        public String File
        {
            get { return (String)GetValue(FileProperty); }
            set { SetValue(FileProperty, value); }
        }

        public static DependencyProperty FileProperty =
            DependencyProperty.Register("File", typeof(String), typeof(RichTextFile),
            new PropertyMetadata(new PropertyChangedCallback(OnFileChanged)));

        private static void OnFileChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RichTextFile rtf = d as RichTextFile;
            if (d == null)
                return;

            ReadFile(rtf.File, rtf.Document);
        }
        #endregion

        #region Functions
        private static void ReadFile(string inFilename, FlowDocument inFlowDocument)
        {
            if (System.IO.File.Exists(inFilename))
            {
                TextRange range = new TextRange(inFlowDocument.ContentStart, inFlowDocument.ContentEnd);
                FileStream fStream = new FileStream(inFilename, FileMode.Open, FileAccess.Read, FileShare.Read);

                range.Load(fStream, DataFormats.Rtf);
                fStream.Close();
            }
        }
        #endregion
    }
}

You can use this new object to load a .rtf file quite easily. I am going to show you how I succeeding in my two use cases using both a regular WPF Application and a WPF Navigation Application.

Part 1 – Using the RichTextFile class in a WPF Application

Use Case 1 – Loading a rich text file

Create a new WPF Application in Visual Studio.

Add the above RichTextFile object to the project.

Add an xmlns reference to our new object. Then add our new object. Notice in our new object that we set IsReadOnly=”True” but we also set IsDocumentEnabled=”True”. This allows for clicking a link even though the document is read only.

<Window x:Class="RichTextFileTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        xmlns:controls="clr-namespace:System.Windows.Controls">
    <Grid>
        <controls:RichTextFile File="{Binding File}" IsReadOnly="True" IsDocumentEnabled="True" />
    </Grid>
</Window>

Code Behind

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Documents;

namespace RichTextFileTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Example example = new Example() { File = "File.rtf" };
            DataContext = example;
        }
    }

    public class Example
    {
        public String File { get; set; }
    }
}

Now the first use case is complete, the rich text file is loading into the RichTextFile control and is visible in the application. However, the second use case is incomplete as clicking the link does nothing.

Use Case 2 – Getting the links to open in a browser

The links can easily made to open in a browser by implementing the Hyperlink.Click event on the RichTextFile. Here is how this is done.

<Window x:Class="RichTextFileTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        xmlns:controls="clr-namespace:System.Windows.Controls">
    <Grid>
        <controls:RichTextFile File="{Binding File}" IsEnabled="True" IsReadOnly="True" IsDocumentEnabled="True" Hyperlink.Click="RichTextFile_Click"/>
    </Grid>
</Window>

Then define the event function in the code behind.

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Documents;

namespace RichTextFileTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Example example = new Example() { File = "File.rtf" };
            DataContext = example;
        }

        private void RichTextFile_Click(object sender, RoutedEventArgs e)
        {
            Hyperlink link = e.OriginalSource as Hyperlink;
            if (link != null)
            {
                Process.Start(link.NavigateUri.ToString());
            }
        }
    }

    public class Example
    {
        public String File { get; set; }
    }
}

The links should now be opening in your browser.

Here is the sample project that demonstrates this.
RichTextFileTest.zip

Part 2 – Using the RichTextFile class in a WPF Navigation Application

How to have the TextBlock in a left column of a Grid in a ListView Template expand or shrink with text wrapping?

Ok, lets say you want to have a Grid where each item is a row of data in a Grid. The left most column should expand or shrink, and yes the text should wrap when it shrinks.

Not so easy…but it can be done if you use the right tools.

  • Use a DockPanel not a Grid
  • Make the left most column the last one added
<Window x:Class="ListBoxWithWrap.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        d:DesignHeight="242"
        d:DesignWidth="388"
        >
    <Grid>
        <DockPanel Name="MainGrid" HorizontalAlignment="Stretch">
            <!-- These four blocks will have other content eventually, but only need to be 45 wide -->
            <TextBlock Text="X" Grid.Column="1" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/>
            <TextBlock Text="X" Grid.Column="2" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/>
            <TextBlock Text="X" Grid.Column="3" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/>
            <TextBlock Text="X" Grid.Column="4" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/>
            <!-- This is the TextBlock that needs to wrap its content (and
                             change the height of the row (so the full content is still
                             visible) to whatever the available space is, but should not
                             make overall ListView wider than the parent's width. -->
            <TextBlock Text="A very long string that should wrap when the window is small." Padding="20,6,6,6" TextWrapping="Wrap" DockPanel.Dock="Right"/>
        </DockPanel>
    </Grid>
</Window>

You will see that this works as you desire.

Now put this in a ListView’s Template and set it to use Binding.

<Window x:Class="ListBoxWithWrap.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        mc:Ignorable="d"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        d:DesignHeight="242"
        d:DesignWidth="388"
        SizeToContent="WidthAndHeight">
    <Grid>
        <ListView Name="lvWrap" ItemsSource="{Binding}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <DockPanel Name="MainGrid" HorizontalAlignment="Stretch">
                        <!-- These four blocks will have other content eventually, but only need to be 45 wide -->
                        <TextBlock Text="X" Grid.Column="1" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/>
                        <TextBlock Text="X" Grid.Column="2" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/>
                        <TextBlock Text="X" Grid.Column="3" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/>
                        <TextBlock Text="X" Grid.Column="4" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/>
                        <!-- This is the TextBlock that needs to wrap its content (and
                             change the height of the row (so the full content is still
                             visible) to whatever the available space is, but should not
                             make overall ListView wider than the parent's width. -->
                        <TextBlock Text="{Binding Content}" Padding="20,6,6,6" TextWrapping="Wrap" DockPanel.Dock="Right"/>
                    </DockPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Window>

Now give it some data to bind to.

using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;

namespace ListBoxWithWrap
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            List<SomeItem> list = new List<SomeItem>();
            list.Add(new SomeItem() { Content = "Some very long string with so many words there should be some wrapping going on to prevent a line of text that is too long" });
            list.Add(new SomeItem() { Content = "Some very long string with so many words there should be some wrapping going on to prevent a line of text that is too long" });
            list.Add(new SomeItem() { Content = "Some very long string with so many words there should be some wrapping going on to prevent a line of text that is too long" });
            list.Add(new SomeItem() { Content = "Some very long string with so many words there should be some wrapping going on to prevent a line of text that is too long" });
            list.Add(new SomeItem() { Content = "Some very long string with so many words there should be some wrapping going on to prevent a line of text that is too long" });

            lvWrap.DataContext = list;
        }

        public class SomeItem
        {
            public string Content { get; set; }
        }
    }
}

The shrink with text wrapping no longer works once inside of the ListView. So that tells you that something to do with the ListView is breaking the feature you want.

Here is how you fix this:

1. Open your project in Expression Blend. (If you don’t have Expression Blend, maybe just look at my code below and copy it)

2. Right-Click on the ListView in the Object and Timeline tab and choose Edit Template | Edit a Copy.

3. Click OK on the next Window.

This will create the following resource code.

<Window.Resources>
		<SolidColorBrush x:Key="ListBorder" Color="#828790"/>
		<Style x:Key="ListViewStyle1" TargetType="{x:Type ListView}">
			<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
			<Setter Property="BorderBrush" Value="{StaticResource ListBorder}"/>
			<Setter Property="BorderThickness" Value="1"/>
			<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
			<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
			<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
			<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
			<Setter Property="ScrollViewer.PanningMode" Value="Both"/>
			<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
			<Setter Property="VerticalContentAlignment" Value="Center"/>
			<Setter Property="Template">
				<Setter.Value>
					<ControlTemplate TargetType="{x:Type ListView}">
						<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="1" SnapsToDevicePixels="true">
							<ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}">
								<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
							</ScrollViewer>
						</Border>
						<ControlTemplate.Triggers>
							<Trigger Property="IsEnabled" Value="false">
								<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
							</Trigger>
							<Trigger Property="IsGrouping" Value="true">
								<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
							</Trigger>
						</ControlTemplate.Triggers>
					</ControlTemplate>
				</Setter.Value>
			</Setter>
		</Style>
	</Window.Resources>

4. Now look at what is surrounding the ItemPresenter. Yes, you see the ScrollViewer, which is your problem. Delete it.

5. Build you project.

Success! Now your feature to both expand or shrink with text wrapping is back.

Here is the final XAML.

<Window x:Class="ListBoxWithWrap.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        mc:Ignorable="d"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        d:DesignHeight="242"
        d:DesignWidth="388"
        SizeToContent="WidthAndHeight">
    <Window.Resources>
        <SolidColorBrush x:Key="ListBorder" Color="#828790"/>
        <Style x:Key="ListViewStyle1" TargetType="{x:Type ListView}">
            <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
            <Setter Property="BorderBrush" Value="{StaticResource ListBorder}"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
            <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
            <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
            <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
            <Setter Property="ScrollViewer.PanningMode" Value="Both"/>
            <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListView}">
                        <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="1" SnapsToDevicePixels="true">
                            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                            </Trigger>
                            <Trigger Property="IsGrouping" Value="true">
                                <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <Grid>
        <ListView Name="lvWrap" ItemsSource="{Binding}" Style="{DynamicResource ListViewStyle1}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <DockPanel Name="MainGrid" HorizontalAlignment="Stretch">
                        <!-- These four blocks will have other content eventually, but only need to be 45 wide -->
                        <TextBlock Text="X" Grid.Column="1" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/>
                        <TextBlock Text="X" Grid.Column="2" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/>
                        <TextBlock Text="X" Grid.Column="3" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/>
                        <TextBlock Text="X" Grid.Column="4" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/>
                        <!-- This is the TextBlock that needs to wrap its content (and
                             change the height of the row (so the full content is still
                             visible) to whatever the available space is, but should not
                             make overall ListView wider than the parent's width. -->
                        <TextBlock Text="{Binding Content}" Padding="20,6,6,6" TextWrapping="Wrap" DockPanel.Dock="Right"/>
                    </DockPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Window>

You should now have a little bit more understanding of the ListView template and how to manipulate it, which should translate to other objects in WPF as well.

A quick overview of MVVM

Model View ViewModel (MVVM) is a design pattern based on Model View Controller (MVC) but specifically tailored to Windows Presentation Foundation (WPF).

MVVM is not a framework per se but many frameworks have been created. Here is a list of MVVM Frameworks from Wikipedia.

See the Wikipedia site here: Open Source MVVM Frameworks.

Another blog, has some basic information on many of these here: A quick tour of existing MVVM frameworks

A framework is actually not necessary to implement MVVM and you should seriously consider whether using one is right for your WPF application or not. Many applications do not need much of the features the frameworks provide. However, there are two common classes that all MVVM frameworks contain. These are ViewModelBase and RelayCommand. Though some frameworks may give them different names or implement them slightly differently, they all have these classes. For example, MVVM Foundation names the ViewModelBase differently. It is called ObservableObject, which is more appropriate because it is incorrect to assume that all objects that implement INotifyPropertyChanged are going to be ViewModel objects.

Instead of installing and using an MVVM framework, you could simply include these classes in your application, as these are all you need to implement MVVM.

  • ObservableObject
  • RelayCommand

While these two classes are enough, you may want to investigate how different MVVM Frameworks implement and what else they implement and why. You may find that another feature implemented is exactly what you need in your application and knowing about it could save you time.

Refreshing a button enabled/disabled by RelayCommand.CanExecute

When using the RelayCommand class, a command class used for MVVM development, you may find times when a button that is enabled or disabled based on RelayCommand.CanExecute does not refresh until a keyboard or mouse click.  When this happens you may have to manually force this refresh to occur. You can force the refresh in the ViewModel with this function:

CommandManager.InvalidateRequerySuggested();

If you want to see this in action, you can download the LDPing project I have recently made.

svn co https://ldmsplugins.svn.sourceforge.net/svnroot/ldmsplugins/LDPing

You don’t need to know about LANDesk or know what an LDPing is to understand what is going on enough to see how to use this one function. In the LDPing project, clicking the button starts a background process to run an LDPing. The button disables when clicked. Once the ping is finishes, the button should enable, however, originally it didn’t enable.  I had to add the CommandManager.InvalidateRequerySuggested() function.

Here is the code:

        /// <summary>
        /// This is the Ping method that launches an LDPing as a BackgroundWorker process, using
        /// PingWorker which is an object that inherits BackgroundWorker and adds one property called
        /// PingSucceeded.
        /// </summary>
        private void Ping()
        {
            IsPinging = true;
            ClearPingResults();

            PingWorker worker = new PingWorker();
            worker.DoWork += new DoWorkEventHandler(worker_DoPing);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_PingCompleted);
            worker.RunWorkerAsync();
        }

        void worker_DoPing(object sender, DoWorkEventArgs e)
        {
            Stopwatch timer = new Stopwatch();
            timer.Start();

            // Now lets ping
            PingWorker worker = sender as PingWorker;
            LDPing tmpLDPing = LDPingAction.AgentPing(IPAddress);
            if (tmpLDPing == null)
                worker.PingSucceeded = false;
            else
                worker.PingSucceeded = true;
            timer.Stop();

            // Lets make sure the GUI spinner has at least a little spin
            int i = 1200 - (int)timer.ElapsedMilliseconds;
            if (i > 0)
                Thread.Sleep(i);
            LDPing = tmpLDPing;
        }

        void worker_PingCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            IsPinging = false;
            PingWorker worker = sender as PingWorker;
            if (worker.PingSucceeded)
                ConnectionResult = "Ping Successful.";
            else
                ConnectionResult = "Ping failed.";

            // This makes the UI refresh against the RelayCommand.CanExecute() function.
            // Essentially, this makes the button enable when pinging is done.
            CommandManager.InvalidateRequerySuggested();
        }

Without the CommandManager.InvalidateRequerySuggested() function, the button’s enabled state doesn’t refresh until an event such as a keyboard or mouse click.

How to animate an edit box to slide in and out using Expression Blend

Ok…so I created a simple WPF and MVVM application called My Friends.  This is just a simple applications, the main goal was not to create a My Friend app, but do demonstrate how to make an edit box slide in an out.

The application is too big to give you step by step instructions to create the whole application, so instead, I am going to make it available for download and then tell you how to see the changes.

  1. Download and extract the solution from the link below:
    MyFriends.zip
  2. Open the project in Expression Blend (not Visual Studio).
  3. Get to know the Application. (Ok, I guess you could do this part in Visual Studio…but the intent is to learn an Expression Blend feature.)
    Check out the Person and PersonCollection classes in the Model folder.
    Check out the Views and the corresponding ViewModels and look at the binding statements.
    The MVVM folder has some standard MVVM classes, that if you know MVVM you should be familiar with.
    The business has an XML Serialization helper class.
    The data folder contains an XML that is serialized from the PersonCollection class.
  4. In Expression Blend, open the View\FriendListView.xaml.
  5. In the Objects and Timeline tab, get to know the assets that are being used. (I think they are all standard assets. I don’t think I am using anything that doesn’t come with .NET and Expression Blend by default.)
  6. In the Objects and Timeline tab, specifically look at the PersonDetailsView object.
    Notice it is behind the FriendList.
    This is the edit box that is animated and it will slide to the right when you click edit.
  7. Go to the States tab.
    Look a the State group that is created. There are two groups: EditFriendOpenState and EditFriendClosedState.  The names should be obvious.
    Notice the Transition duration is set to .5 and notice the Easing function.
  8. There is a behavior or action called GoToStateAction that was dragged over the Edit button and configured to change the state to EditFriendOpenState.
  9. The GoToStateAction was also dragged over the X button on the edit page and configured to change the state toEditFriendClosedState.
  10. Build the application and see how the state changes and how it uses the Easing function for .5 seconds to slide the edit field out.

Here is a sample image:

Anyway, I hope you find this a fun sample application to learn with.

 

This project is also an example of:

  • Basic C#
  • Xml Serialization
  • MVVM (including ICommand and RelayCommand with buttons)
  • Using Data Converters (as one converter is used)

Exercises for further learning:

LINQ exercise

  • There is a comment in the View\FriendListView.xaml that uses LINQ, but there is a reason LINQ wasn’t used, can you determine the reason.
  • What would you have to do to use LINQ and make the application work?

DateTime exercise

  • There is an Age property that was left commented out.  Uncomment it, make it read-only and make it provide the correct number in years based on the Birthday.

How to make a transparent cube in Expression Design

In order to provide a good example of using the polygon and square tool, here is how to make a square look 3D.  Be aware that is a 2D image made to look like 3D.

Transparent Cube

What you will learn

  • Using the rectangle tool
  • Using the polygon tool
  • Make edges by simply using slight color variations
  • Transparency
  • Front vs back placement of element

Step 1 – Create a new Expression Design document

  1. Open Expression Design and click File | New.
  2. Name the file TransparentSquare.design (or whatever name you would like).
    Note: I used a custom width and height of 300x 300, and left the resolution at 96.
  3. Click OK.

Step 2 – Create the first square

This first square will be the face of the cube.

  1. Click the Rectangle on the ToolBox.
    Note: If you cannot find it, try pressing “M” as that is the shortcut key.
  2. Draw a rectangle on the screen while holding down the shift key(the shift key is what tells Expression Design that you are looking for a perfect square and not a rectangle).
    Note: The X and Y placement of the square are: 131.5, 160. The width and height of my square are both 200.2 px. The Rotation Angle and Skew Angle are both 0.
  3. With the square selected, go to the properties tab.
  4. Under Appearance, click the Fill tab.
    Note: Hold your cursor over the tab and it will tell you which tab is the Fill tab and which is the Stroke tab.
  5. Select the Gradient option.
    Note: It is the bottom left of the four boxes just left of the color boxes. Once you click this, you will have a gradient bar.
  6. To the right of the gradient bar, there are two gradient options: Linear Gradient, Radial Gradient.
    Choose Linear Gradient.
  7. On the gradient bar, you should see two markers (squares with a triangle on top).
    Click the left marker and set the color to a nice blue.
    I used #5A78D8.
  8. Click the right marker and set the color to a blue that is darker than the first one you selected.
    Note: I used color #153FC4.
  9. Click the Stroke tab.
  10. Under the Stroke tab and all the way under the color picker, look for a drop down menu. (By default this menu has an image of a black line but can have many options.)
  11. Change it to No Stroke.
  12. Change the Stroke Width to 0 px.
  13. For Transparency, there is an Opacity setting. In case you didn’t know, Opacity is the antonym of Transparent, so setting the Opacity to 90% would be the same as setting a Transparency of 10%.
    Set the Opacity to 90%.

Step 3 – Create the right side of the cube using the Polygon tool

  1. Click the Polygon on the ToolBox.
    Note: If you cannot find it, try pressing “J” as that is the shortcut key.
  2. Draw a polygon on the screen just to the right of the square.
    The size doesn’t really as it will get its size as we move the corners into place.
    Note: The width and height of my square are both 200.2 px.
  3. With the polygon selected, go to the properties tab.
  4. Changes the points from 4 (there are only 3 points by default).
  5. Click Direct Selection on the ToolBox.
    Note: If you cannot find it, try pressing “A” as that is the shortcut key.
  6. Click the bottom left corner of the Polygon and drag it to exactly the bottom right corner of the first square.
    Note: Zooming in can really help you get the corners to line up.
  7. Click the top left corner of the Polygon and drag it to exactly the top right corner of the first square.
  8. Make sure the left side line of the polygon is perfectly straight up and down to match the square.
  9. Click the top right corner of the polygon and drag somewhere up and to the right at an angle that seems to be a good perspective angle.
  10. Click the top right corner of the polygon and drag somewhere up and to the right at an angle that seems to be a good perspective angle, probably a little bit sharper an angle than the top angle.
  11. Make sure the right side line of the polygon is perfectly straight up and down to match the square
  12. Remove the Stroke.  You should already know how to do this now.
  13. Set the Fill to a single color that is a slight variation of the blue we used for the face of the square.
    Note: I used #002DB8.
  14. Set the Opacity to 90%.

Step 4 – Create the top side of the cube using the polygon tool

Note: These steps are a little more general as you have already performed these above.

  1. Click the Polygon on the ToolBox.
  2. Draw a polygon on the screen just to the top of the square face.
  3. Changes the points from 4 (there are only 3 points by default but since you already chose 4 it maybe is chosen for you now).
  4. Click Direct Selection on the ToolBox.
  5. Move the four corners of the polygon into place.  Three of the corners are already positioned for you. Only the top left corner is not.  Make sure to give it a little more angle to make the perspective look right.
  6. Remove the Stroke.
  7. Set the Fill on top to gradient and use a lighter blue.
    Note: I used#92A7EB for the left gradient stop and #7B97EB for the right gradient stop.
  8. Set the Opacity to 90%.

Step 5 – Create a the back of the cube

Ok, well only give a few steps this time. You should be able to do this without perfect step by step instructions, now.

  1. Copy and paste the square face (that way you get the Stroke and Fill and Opacity settings already configured).
  2. Notice that three of the corners are placed for you so you can simply resize the new a square and connect it to the three corners.
  3. You may find that this doesn’t look quite right because this square is in front of the other items you have already created.  Right-click on it and choose Arrange | Send to back.

Step 6 – Create the left side of the cube using the polygon tool

Ok, you now have created two polygon sides. You now need to create a third.  No hand-holding this time.  Just create it very similar to how you created the previous two.  It would be nice if you could just copy the right polygon and have it fit perfectly but it doesn’t fit.  Do you know why? That is right, due to perspective the angle is different so the left side is a little bigger than the right side.  However, you can still use copy and paste, you just have to place the corners.

You don’t need to create a bottom for the cube because it just appears to be already created due to transparency.

You are finished!

Click the Sphere to download a zip file containing: BlueSphere.design, a BlueSphere.png and a BlueSphere.xaml.

Transparent Cube

 

Download