Latest Posts
Starting Point
In my earlier post, the HiliteTextBox, I created a simple WPF UserControl which is hosted in an ElementHost in a Windows Form. If the user tries to continue without entering text in the TextBox, a bright colored Border would appear around it and stay there until the user does enter something.

If we are going to go to the trouble of harnessing WPF's graphics power, we may as well go a step further and add some animation to the control. One simple thing we can do is to animate the Border Thickness.
This is the XAML for the UserControl I created previously. The only thing I have changed is the class name. It was originally HiliteTextBox, but for this version it will be named AnimatedTextBox :
<UserControl x:Class="AnimatedTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="300" Height="300">
<UserControl.Resources>
<LinearGradientBrush x:Key="BrightGradient" EndPoint="0.056,0.993" StartPoint="0.634,0.342" SpreadMethod="Repeat">
<GradientStop Color="#FFB50D0D"/>
<GradientStop Color="#FFE80C2C" Offset="1"/>
<GradientStop Color="#FFF4DF5A" Offset="0.17399999499320984"/>
<GradientStop Color="#FFCA0C1F" Offset="0.357"/>
<GradientStop Color="#FFDCD762" Offset="0.522"/>
<GradientStop Color="#FFB70D36" Offset="0.716"/>
<GradientStop Color="#FFDDEE49" Offset="0.823"/>
</LinearGradientBrush>
</UserControl.Resources>
<Border x:Name="GradBorder"
BorderBrush="LightGray"
BorderThickness="5"
CornerRadius="4,4,4,4">
<TextBox x:Name="InputTextBox"
TextWrapping="Wrap"
TextChanged="InputTextBox_TextChanged" >
</TextBox>
</Border>
</UserControl>
And this is the code-behind in the UserControl:
Private Sub InputTextBox_TextChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.TextChangedEventArgs)
HighlightEmpty()
End Sub
Public Sub HighlightEmpty()
If InputTextBox.Text.Length > 0 Then
GradBorder.BorderBrush = New SolidColorBrush(Colors.Black)
GradBorder.BorderThickness = New Thickness(1)
Else
GradBorder.BorderBrush = FindResource("BrightGradient")
GradBorder.BorderThickness = New Thickness(4)
End If
End Sub
Public Function IsTBEmpty() As Boolean
If InputTextBox.Text.Length = 0 Then
Return True
Else
Return False
End If
End Function
Creating the Animation in Code
Generally, you will find XAML to be the best tool for creating animations - and if they are quite complex then Expression Blend is your best bet. However, you can create a simple animation in the UserControl using code. The following code-behind in the UserControl will cause the Border to change in size over a short period of time:
Public Sub ChangeSize()
Dim ThickAnim As New ThicknessAnimation
With ThickAnim
.To = New Thickness(14)
.AutoReverse = True
.Duration = New Duration(TimeSpan.FromSeconds(0.6))
End With
Me.GradBorder.BeginAnimation(Border.BorderThicknessProperty, ThickAnim)
End Sub
Even if you have no experience of WPF, most of the above code is relatively easy to follow.
ThicknessAnimation is a class that 'does what it says on the tin', as we say here in the UK - it does exactly what you would expect. It animates the Thickness Property of a target element.
To sets the value of the Thickness that has to be reached by the conclusion of the animation.
AutoReverse ensures that the Thickness returns to its starting value by the end of the animation sequence.
Duration assigns the length of time for which the animation will run. Note that in the case of this AutoReversed animation both the increase (The To value) and the decrease (AutoReverse to the start value) in size are completed within the allowed Duration.
The BeginAnimation Method takes parameters of a kind that might be unfamiliar. It takes the WPF DependencyProperty for the Border class as the first argument and it takes the ThicknessAnimation instance as the second argument. The correct element is animated, of course, because the BeginAnimation method is applied to GradBorder, the Border instance we are dealing with.
Me.GradBorder.BeginAnimation(Border.BorderThicknessProperty, ThickAnim)
To get the animation to run, we can incorporate it into the code we used previously that tests if the TextBox is empty. This code is in the Windows Form on which we placed an ElementHost to house the WPF UserControl :
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If AnimatedTextBox1.IsTBEmpty = True Then
' Change to bright gradient border
AnimatedTextBox1.HighlightEmpty()
' Animate
AnimatedTextBox1.ChangeSize()
' Return focus
Me.ElementHost1.Focus()
Else
MessageBox.Show("You are clear to continue")
End If
End Sub
If you try this code and run the project, click the Continue Button with no text in the TextBox, you will see that the Gradient Border appears and then the Border size is animated.
One thing you may have noticed is that the target in the Button Click event is an object named AnimatedTextBox1. If you scour through the small amount of code I have written so far, you won't find where I created this object. The reason being that I didn't. When you use the Smart Tag in the ElementHost and select the content, it will automatically create an instance for you and name it.
In the next part, I will create a different animation using what on the face of it seems to be a very complex block of XAML. When we take a closer look, you will see that it is not as complicated as it first appears.
Introduction
A recent forum post on VBCity asked for a way to highlight an important TextBox in a Windows Form. Assuming that the plan is to highlight the TextBox if the user is about to leave the Form without entering any text into it, there are of course several ways this can be done using Windows Forms techniques. But I thought it might be interesting to try something different and see how easy it would be to create a WPF UserControl that incorporated a TextBox, a smack-you-in-the-eye gradient and a border with rounded corners*. I picked those last two features because they are not particularly easy to create in Windows Forms. The WPF UserControl would then have to be integrated seamlessly into the Windows Forms environment.
(*If this isn't cutting-edge enough for you, I will be looking at adding animation to the control in a follow up blog post).
So here's our demo Windows Form that will contain two TextBoxes. The WinForms TextBox is already in place. We will create the WPF one shortly.

The WPF UserControl
The first step is to add a WPF UserControl to the Windows Forms project. This is as simple as hitting Ctrl+Shift+A and selecting "WPF" from the Add New Item menu which appears. The only choice you will then see is "User Control (WPF)", so select this and change the default name to 'HiliteTextBox.xaml'. Then click the Add button.

The WPF UserControl will be added to your Windows Forms project and behind the scenes all the required References will be added also. The UserControl will be on display as the selected item in Visual Studio and you will see that all the WPF tools are in the Toolbox, not the WinForms ones.
Delete the default Grid from the UserControl and then drag a Border element from the Toolbox and drop it on the XAML pane, on one of the empty lines where the Grid used to be. You might think it strange that you drag a visual element and drop it into the markup area. There's nothing to stop you from dropping it in the Design pane instead. If you do this, you will find that several properties are automatically added for you and, as it happens, we don't want those particular properties set and you would therefore have to delete them.
Assign the Name 'GradBorder' to the Border. Create an empty line between the opening and closing tags of the Border. Drag a TextBox from the Toolbox and drop it on the empty line. Name the TextBox 'InputTextBox'.
Here's the Markup as at this point:
<UserControl x:Class="HiliteTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="300" Height="300">
<Border x:Name="GradBorder">
<TextBox x:Name="InputTextBox"></TextBox>
</Border>
</UserControl>
Don't worry that nothing seems to be happening in the Design pane. We'll soon change that. With the Border selected in the XAML pane, move over to the Properties window and change the BorderBrush to LightGray, the BorderThickness and CornerRadius properties to 4. (Still no change in the Design pane; no worries).
The plan is to have a plain gray border round the TextBox by default and have this change to a bright gradient if the user tries to continue without entering any text in the TextBox. To set this up, first create a new empty line between the opening tag of the Window and the opening tag of the Border. Copy and paste the following markup, which creates a LinearGradientBrush that will be stored as a Resource.
<UserControl.Resources>
<LinearGradientBrush x:Key="BrightGradient" EndPoint="0.056,0.993" StartPoint="0.634,0.342" SpreadMethod="Repeat">
<GradientStop Color="#FFB50D0D"/>
<GradientStop Color="#FFE80C2C" Offset="1"/>
<GradientStop Color="#FFF4DF5A" Offset="0.17399999499320984"/>
<GradientStop Color="#FFCA0C1F" Offset="0.357"/>
<GradientStop Color="#FFDCD762" Offset="0.522"/>
<GradientStop Color="#FFB70D36" Offset="0.716"/>
<GradientStop Color="#FFDDEE49" Offset="0.823"/>
</LinearGradientBrush>
</UserControl.Resources>
There is a very good reason for storing the gradient brush separately and naming it with the 'BrightGradient' Key, as you will see soon.
We will set a few properties on the TextBox. Select the TextBox in the XAML pane and then move to the Properties Window and set the TextWrapping property to 'Wrap'.
In the XAML pane, create a new empty line inside the opening tag of the TextBox. Then type in: 'TextChanged='. As soon as you do this, an Intellisense popup will appear, inviting you to create a new event handler for the TextChanged event.

Press the TAB key and the rest of the line will be completed for you. It will be assigned a default name and this event handler will have been added to the code-behind for you.
Your markup for the Border and TextBox should now look like this:
<Border x:Name="GradBorder"
BorderBrush="LightGray"
BorderThickness="1"
CornerRadius="4,4,4,4">
<TextBox x:Name="InputTextBox"
TextWrapping="Wrap"
TextChanged="InputTextBox_TextChanged">
</TextBox>
</Border>
Open up the vb file for the UserControl - HiliteTextBox.xaml.vb - in the project files in Solution Explorer. The InputTextBox_TextChanged event handler will be there for you.
Before we code the event handler though, first create a Sub that will check if the TextBox contains any Text. If it does, the Border will be set to the non-highlighted LightGray; if it doesn't, the Border will be the bright gradient. Enter the following code:
Imports System.Windows
Imports System.Windows.Media
Public Sub HighlightEmpty()
If InputTextBox.Text.Length > 0 Then
GradBorder.BorderBrush = New SolidColorBrush(Colors.Black)
GradBorder.BorderThickness = New Thickness(1)
Else
GradBorder.BorderBrush = FindResource("BrightGradient")
GradBorder.BorderThickness = New Thickness(4)
End If
End Sub
The only code that may be new to you might be the FindResource method. This is a really useful feature in WPF, which allows you to ferret through the project and pull out any specific resource that you want to use. The 'BrightGradient', you will recall, is the Key that I assigned to the LinearGradientBrush that I stored as a UserControl Resource earlier.
The Imports statements at the top of the file ensure that you are accessing elements in the System.Windows namespace.
Hosting the UserControl
Now the UserControl is built, it has to be hosted in the Windows Form. Rebuild the project and then select the Form1.vb file. The control that WinForms uses to host WPF UserControls is the ElementHost. This probably won't be in your Toolbox by default, so if it isn't listed, right-click on the Toolbox and select Choose Items... Select the ElementHost from the .NET Framework Components Tab.
Drag an instance of the ElementHost from the Toolbox on to the surface of the Form. It will be too large by default, but before you change its size, click on small down arrow next to 'Select Hosted Content' and select HiliteTextBox from the list:

The Smart Tag window will close and you can then adjust the size of the ElementHost. As you can see from the screenshot, an error message appears inside the ElementHost. You can safely ignore this as it is only tells you that the Windows Form can't display the visuals of the UserControl at Design time.
Next we need to add code to the Continue button. For our demo purposes, this will be sufficient:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
HiliteTextBox1.HighlightEmpty()
End Sub
If you look back at the earlier code, you will know that the HighlightEmpty method checks if the TextBox has Text entered and sets the Border accordingly. Although the Button on the Windows Form can't access the graphical elements of the UserControl directly, the HighlightEmpty procedure allows for this interchange between the two.
Try running the application now. Don't enter any text into the UserControl, then press the Continue button. You should see the highlight gradient border appear around the TextBox.

If you then insert some text and press the Continue button again, the highlight border will be replaced with the gray one.
Adding Some Refinements
That pretty much fulfills the spec, but there are a couple of things I would definitely add. The first feature is that if the user is warned about not entering text, then I would like to give some positive feedback as soon as they do so. That is, before they press the Continue button for a second time. This is easily achieved by adding a line of code to the TextChanged event handler of the InputTextBox.
Go back to the WPF UserControl code-behind file and add a call to the HighlightEmpty procedure:
Private Sub InputTextBox_TextChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.TextChangedEventArgs)
HighlightEmpty()
End Sub
When you run the application again and press the Continue button with no text in the TextBox, the highlight border appears as before. Now though, when you enter a character, the highlight Border disappears. As a small bonus, if the user then deletes all the text in the TextBox, the highlight will reappear, just to make certain they know about it.
The second thing I would like to add is a way of giving the developer a choice of actions on the Continue Button Click event. What I mean by this is that at the moment, the call goes to the HighlightEmpty procedure of the UserControl. This approach makes it difficult to build choices into the Button Click event, along the lines of "Depending whether the TextBox is empty, do something here in the Form's logic".
There are several easy ways of doing this, including adding a Boolean Property to the UserControl or creating a simple Function. This tests if the InputTextBox is empty and if it is, it Returns True. Based on the returned value, the Button event handler can follow one course of action or another.
Here's a Function that can be added to the UserControl:
Public Function IsTBEmpty() As Boolean
If InputTextBox.Text.Length = 0 Then
Return True
Else
Return False
End If
End Function
You can then put whatever code you want in the Button Click event handler. Maybe something like:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If HiliteTextBox1.IsTBEmpty = True Then
HiliteTextBox1.HighlightEmpty()
Me.ElementHost1.Focus()
Else
MessageBox.Show("You are clear to continue")
End If
End Sub
You can apply this approach of using a WPF UserControl in a Windows Form to get access to lots of those enhanced visual goodies that WPF offers, including Animation. As you have seen, creating a WPF UserControl hosted in an ElementHost is a very easy process. As an alternative to fiddling with individual controls on a Windows Form, you can of course include complete WPF Windows in Windows Forms projects.
In this article, we will look at how to alter the background color that appears when a user selects an item in a WPF ListBox
I've been using DataTemplates to organize the layout of WPF ListBoxes for quite a while now. Not only in my WPF Applications, but also by using WPF/WinForms Interop (See my article on devcity for info on how to do this).
If you have used DataTemplates yourself, you will know that they can make a great job of packaging up the data display and allowing you to easily create UIs that would be extremely difficult in Windows Forms.
I was working on a ListBox creation task this week. Nothing very fancy - each item comprised of an Image and two items of text inside a Gradient Border:

The DataTemplate makes a fair job of tweaking the presentation of the data, but when I ran it and selected an item I still got the standard solid blue selection color:

I thought it would be better if I could replace the standard solid blue with something else, but this wasn't something I could achieve by tweaking the DataTemplate. What is needed is a change to the ItemContainerStyle .
It's important to understand the role of these two styling tools. The DataTemplate allows you to package up the individual items of data for display. In the case of a ListBox, you can think of the DataTemplate as sitting inside the ListBoxItem. The ItemContainerStyle is one level up from this. It is responsible for the overall look of the ListBoxItem. So if it helps you to picture the respective roles, you could think of the DataTemplate being contained in the ItemContainerStyle, although that's not exactly how it works.
The question is, how do you change the ItemContainerStyle to create a different selection color and, once this is done, how do you then get the ListBox to apply it?
The answer to the second question is shorter, so I'll deal with that one first. ItemContainerStyle is a property of the ListBox.
<ListBox ItemContainerStyle="{StaticResource SimpleListBoxItem}">
As you would expect, this property takes a Style as its value. And that leads us to the answer to the first question - you create a Style, but (and here's the important bit) you ensure that the Style also contains the ControlTemplate for the ListBox.
When you stop and think about it, this has to be right. As soon as you find you want to change the fundamental look of the ListBox element, you know you have to work with its Template. How much trouble you then go to in order to rewrite the ControlTemplate is a matter of choice.
I began by looking at the full ListBox template, which is fairly lengthy, but then realized two things:
- I already had my DataTemplate in place to do the fancy stuff, so all I needed to do was find the part of the Template that deals with the change of color when an item is selected and tweak this. Everything else could be left unchanged.
- I didn't need to make any changes to the ListBox template. What I was dealing with here was the ListBoxItem template.
When the requirement is as simple as this, a useful approach is to use the Simple Styles, which are available from many sources. If you haven't come across these yet, they are a cut-down version of the full Styles that are used to create the standard WPF elements. One easy way to access and download them is to enter "Styling with ControlTemplates Sample" in the "Look For" text box of the Index, (not the online Search box) on the Visual Studio Help page. This will reveal the sample page, which includes a link to download all the simple styles.
So I took the simple style version of the ListBoxItem and changed the Setter for the IsSelected Trigger. Here is the finished Style:
<Style x:Key="SimpleListBoxItem" TargetType="ListBoxItem">
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border
Name="Border"
Padding="2"
SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Border" Property="Background"
Value="{StaticResource AuthorGradient}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Compared to the full version, that's fairly brief. You will see that I changed the Background color of the Border that contains the ListBoxItem in the Template* from the default to a gradient brush named AuthorGradient that I created and stored in the App.xaml file as an Application scope Resource.
* Don't confuse the Border in the Style's Child ControlTemplate with the Border that is used in the DataTemplate.
Now when an item is selected, the user sees this gradient instead of the plain blue:
So the full markup for the ListBox is as follows:
<ListBox x:Name="TemplatedListBox"
HorizontalAlignment="Left" Margin="10"
ItemTemplate="{StaticResource GreenDataTemplate}"
ItemsSource="{Binding}"
ItemContainerStyle="{StaticResource SimpleListBoxItem}" >
</ListBox>
You can see that the ListBox's ItemTemplate is set to the DataTemplate which controls the layout of the data in each ListBoxItem. The ItemContainerStyle contains the template for the ListBoxItem - the one where I changed the selected item color from plain blue to gradient green.
In order for this particular example to work, you will of course need to have a DataSource available to the ListBox's ItemsSource, to which it can bind. However, you don't need to have a bound ListBox, as you will see at the end of this item.
The DataTemplate I used to create the Border/Image/TextBlocks combination is:
<DataTemplate x:Key="GreenDataTemplate" >
<Border BorderBrush="{StaticResource GreenGradient}" BorderThickness="4"
CornerRadius="4" Margin="4,2">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=ImagePath}" Margin="2,0,4,0"
Width="60" Height="70" Stretch="UniformToFill" />
<StackPanel Margin="1" Width="120">
<TextBlock Padding="2,17,5,2"
FontSize="13"
FontWeight="Bold" Foreground="DarkGreen"
Text="{Binding Path=Name}" Height="56">
</TextBlock>
<TextBlock Foreground="Green"
Margin="12,2,0,2"
Text="{Binding Path=Published}" >
</TextBlock>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
I placed this in the Window.Resources block. Again, this will only work if you have a DataSource which contains the three fields I have used in the Bindings for the Image and TextBlocks - ImagePath, Name and Published.
If all this DataBinding and DataTemplates is confusing or more than you need, and you simply want to change the selected item background color, then the following ListBox will be sufficient:
<ListBox ItemContainerStyle="{StaticResource SimpleListBoxItem}">
<ListBoxItem> <TextBlock Margin="4" Padding="5" FontSize="14">Item 1</TextBlock></ListBoxItem>
<ListBoxItem>
<TextBlock Margin="4" Padding="5" FontSize="14">Item 2</TextBlock>
</ListBoxItem>
</ListBox>
When an item is selected, the ItemContainerStyle will kick in and the user will see the gradient:

And finally, if you don't even want the gradient, but just need a different solid color, then simply make the change in the Style:
<Style x:Key="SimpleListBoxItem" TargetType="ListBoxItem">
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border
Name="Border"
Padding="2"
SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Border" Property="Background"
Value="Yellow"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As you can see, in this case, I have changed the highlight color to Yellow by editing the Setter for the Border in the IsSelected trigger. The result will be:

So, whether you want to create multicolor effects, tied in with bound DataSources, or even if you simply want to change the selected item solid color, you now know how to do it.
This will be something of a 'Work in Progress' blog item, as I plan to jot down differences between WPF and Silverlight as I find them, either in my own work or from other sources. It can be difficult to keep up to speed on what the precise differences are at any given time. The SDKs, Toolkits, roll out of new editions of both platforms, etc all cause the sands to keep on shifting. Anyhow, I hope those that I do identify will be of help to those of you who are familiar with WPF, but maybe not so much with Silverlight.
The topic headings are listed alphabetically.
Button Content
In WPF, you can create a Button and assign its Content in the following way:
<Button Margin="5" >Silverlight< SPAN>Button>
In Silverlight, you don't have this facility and have to explicitly identify the Content property:
<Button Margin="5" Content="Silverlight">< SPAN>Button>
Button ClickMode
This is more a matter of user expectation than different properties. The WPF Button has a ClickMode property. This is available in both WPF and Silverlight. The three available values are:
As you would expect, when the Click event of the button will fire depends on this particular setting.
The reason why I include it as a difference is that I think it is unlikely that you will often use the 'Hover' option in a desktop application, because that isn't a reaction your users will expect. In a Silverlight, browser based environment, of course, it may well be that users will be happy with a button click firing when they hover over a button. (Actually, I'm not sure that 'hover' is totally accurate. It really seems to fire on the immediate mouse enter action and I haven't found a setting that allows a built in delay.)
The default value - both for WPF and Silverlight - is 'Release'.
Chrome and Decorators
Most of the Chromes and Decorators, which can be used to fine tune control templates to a very detailed level, are not available in Silverlight2.
Commands
Silverlight 2 does not support the WPF Commands structure. It does however support the ICommand interface.
Controls (Visual Elements)
It's hard to keep up with the permutations and changes, but I think the following breakdown is accurate:
1. Available in WPF (.NET 3.5, Visual Studio SP1), but not in Silverlight 2:
- FlowDocument Readers
- Frame
- Glyph
- GroupBox
- InkCanvas
- ListView
- Menu
- NavigationWindow *
- Page *
- PageFunction *
- RichTextBox
- StatusBar
- UniformGrid
- VirtualizingStackPanel
- WebBrowser
- Window
- WindowsFormsHost
* These items are not necessary in Silverlight, because it is browser based.
2. Available in Silverlight 2 , but not in WPF with Visual Studio 2008:
- DataGrid *
- DatePicker *
- HyperlinkButton
- MultiScaleImage
* These are not included in WPF in Visual Studio 2008, but are downloadable as part of the WPF Toolkit and will almost certainly ship with VS 2010. You can see info and download the WPF Toolkit from here.
3. Available in the Silverlight 2 Toolkit and in WPF in Visual Studio 2008.
- Calendar
- Expander
- GridSplitter
- TabControl
- TreeView
- ViewBox
- WrapPanel
4. Available in the Silverlight 2 Toolkit, but not in WPF.
- Accordion
- AutoCompleteBox
- ButtonSpinner
- Data Visualization/Chart Tools
- DomainUpDown
- NumericUpDown
- TimePicker
- TransitioningContentControl
5. Available in the WPF SDK, but not in Silverlight 2.
DataBinding
- Silverlight 2 does not support One-Way To Source binding.
- With WPF, the default binding mode is dependent upon the dependency property, but is usually TwoWay. The default binding mode in Silverlight 2 is OneWay.
- Silverlight does not support Explicit DataBinding. It does however support the key approaches of LostFocus and PropertyChanged.
- Silverlight 2 does not support MultiBinding.
- Silverlight 2 does not support Element Binding (e.g. Binding a Slider value directly to a property on another element).
- Silverlight 2 does not support the ObjectDataProvider.
DependencyProperties
Silverlight 2 does not support Read Only DependencyProperties.
Deployment Options
A Silverlight application is deployed to a web server and hosted in a web browser. (The next version of Silverlight should also include the ability to view the application offline). WPF applications may be deployed as a standard installed application or via Click Once. WPF also offers a browser hosted version - the XAML Browser Application (XBAP). In view of the limitations of XBAPs it is likely that most developers would now choose Silverlight over WPF for browser hosted applications.
DrawingBrush
The WPF DrawingBrush is not supported in Silverlight 2. However, there is little that this brush does that can't be achieved with other approaches. The key loss would be the ability to tile, but there are workarounds for this.
Effects
Effects (as opposed to BitmapEffects, which are no longer recommended for use) are only available in WPF. The next version of Silverlight should have the ability to create Pixel Shaders, and therefore Effects.
File Access
WPF has full access to local files in the same way as other .NET desktop applications. Silverlight runs in a Low Trust environment, so Users have restricted access to local files. Access to files in Silverlight is only available by means of the OpenFileDialog and even then is effectively only Read Only access. In Silverlight 2, saving data to file can only be achieved by using Isolated Storage.
Hardware Acceleration
By default, WPF hands off much of the heavy duty graphics work to the GPU, assuming that the graphics card meets the minimum specification. Silverlight uses software rendering.
Hyperlinks
In WPF you can create a Hyperlink by using the Hyperlink element. Generally you use these in Page based applications and FlowDocuments. Silverlight uses the HyperlinkButton.
Both elements use the NavigateUri property to assign the address of the link. The Silverlight version seems to be more versatile. Although it is called 'button', it doesn't have the standard rectangular button appearance. In fact, if you drag one from the Toolbox, you will see its markup in the XAML pane, but nothing shows in the Design pane.
The intention is that you use the HyperlinkButton as a canvas (with a small 'c', not a Canvas element). You then add Content to the button and this is what creates the interface that the user sees. The Content can be anything from a simple single element to a more complex interface - such as a StackPanel which contains multiple elements:
<HyperlinkButton NavigateUri="http://www.devcity.net" Width="60"
Margin="15" HorizontalAlignment="Left"
VerticalAlignment="Top">
<HyperlinkButton.Content>
<StackPanel>
<Image Source="ShelfUnit_WithCans.png">< SPAN>Image>
<TextBlock> Click Me! < SPAN>TextBlock>
< SPAN>StackPanel>
< SPAN>HyperlinkButton.Content>
< SPAN>HyperlinkButton>

Isolated Storage
There are significant differences between the implementation of local isolated storage in Silverlight 2 and WPF. Silverlight 2 restricts the availability of disk space. The default setting is 1 MB. This may be increased with explicit user authorization.
Key Enumeration
WPF contains more precise Keyboard Key definitions in its Key enumeration. For example, it
KeyEventArgs
WPF implements several properties in KeyEventArgs; Silverlight implements two - Key and PlatformKeyCode.
WPF does not implement PlatformKeyCode. This is used in Silverlight to identify non-Windows keys.
Markup Extensions
Silverlight only supports a small sub-set of markup extensions. They are:
- Binding
- StaticResource
- TemplateBinding
- x:Null
It is not possible to create your own markup extensions in Silverlight.
Mouse Events
Silverlight supports a limited set of Mouse events:
- LostMouseCapture
- MouseEnter
- MouseLeave
- MouseLeftButtonDown
- MouseLeftButtonUp
- MouseMove
This means that Silverlight doesn't support mouse wheel, left/right mouse button differentiation or any of the PreviewXX mouse events that are available in WPF.
Network
Silverlight 2 is limited to asynchronous network calls.
Silverlight 2 can communicate to Network Resources via Sockets.
Silverlight 2 supports only SOAP 1.1 over HTTP, and does not support other web services not compliant with WS-I Basic Profile 1.0.
Path Based Animations
This feature is not available in Silverlight 2. Linear interpolated and KeyFrame animations are available.
Printing
No PrintDialog or Printing facility in Silverlight 2.
Resources
Silverlight 2 does not support MergedDictionaries.
Silverlight 2 does not include the FindResource method.
RoutedEvents
Silverlight 2 only supports Bubbling and Direct Routed Events. WPF also supports Tunneling Events.
Security
WPF desktop applications require FullTrust permission in order to run. XBAPs run with partial trust. Silverlight 2 applications run in low trust.
Styles
WPF offers the choice of Named Styles and Typed Styles. Named Styles allow you to set the Style on an element by using its Key. Typed Styles do not have a Key and all elements of the TargetType within scope of the Style will be assigned the Style.
Silverlight doesn't recognize Typed Styles and if they are used, the assignment of the Style will fail silently.
Silverlight doesn't allow the use of the 'BasedOn' feature to inherit from Styles.
When setting the TargetType, Silverlight does not use the markup extension. You simply assign the name of the TargetType in double quotes. If you forget, and force of habit makes you add the curly braces and markup extension in Silverlight, you won't get a design time error, but it will fail at run time.
3D Graphics
Silverlight doesn't have the full range of 3D creation tools that are available in WPF.
Tiled Brushes
Silverlight 2 doesn't support the TileMode in ImageBrush.
Transitions
Silverlight will not accept Triggers and uses the Visual State Manager (VSM) to organize animations and transitions for user interaction. VSM's capabilities currently are limited to the equivalent of standard Style Triggers and EventTriggers. There are no features equivalent to DataTriggers or MultiTriggers in Silverlight.
VSM for WPF will be available soon.
Validation
WPF supports the creation of ValidationRules. This feature is not available in Silverlight 2.
Visual Brush
Silverlight2 doesn't support WPF's Visual Brush. As this is a brush of very limited use, it's not likely to be much of a problem.
Visual State Manager (VSM)
Currently this is a Silverlight only feature. It will be introduced into WPF in the future.
Visual Studio Designer
Visual Studio 2008 has the facility for you to drag and drop, reposition, resize, etc, elements in the Design pane with WPF. For Silverlight it seems to offer editing tools only in the XAML pane. The Design pane is a kind of 'Read Only' version of what is created in the markup.
VisualTreeHelper
This class exists in both WPF and Silverlight 2, but because of differences in the underlying base classes of the two platforms they have different implementations. The key difference is that WPF offers the HitTest method, whereas Silverlight2 uses FindElementsInHostCoordinates.
With the exception of GetChild, GetParent and GetChildrenCount, Silverlight2 doesn't support any of the GetXXX methods found in WPF.
Watermarked TextBox
This is a Silverlight only control. The name says it all - basically a TextBox that allows you to include light shaded text to help the user. If memory serves, this was in Kevin's famous Bag o'Tricks.
Note: Most of the differences described above are also included in an excellent White paper comparison document published by Wintellect. You can download a copy from here.
As a Developer, Visual Studio is my comfort zone but I thought the time had come to see what the experience of creating a Silverlight 2 project in Expression Blend would be like. Having spent the past six months working almost exclusively on Windows Presentation Foundation, I was particularly interested to find out how different the WPF and Silverlight experience would be.
The first thing I had to do was update my copy of Expression Blend 2. Out of the box, your only Silverlight option is a Silverlight 1 site. But if you go to the download site here you can download the latest Service Pack. This incorporates the changes that were available in the Blend 2.5 Beta, the main one being that you can build a Silverlight 2 Application.
Probably the biggest difference between the two versions of Silverlight is that Silverlight 2 gives you the option of using C# or Visual Basic as the code-behind. If, like me, you're not a Javascript expert, but have served your time on the .NET learning curve, this is a really welcome option.
If you have used WPF, you will know that by default when you create a new WPF Application you are presented with a Window instance containing a Grid as the root element on which to start creating your UI. In Silverlight 2, the default is a user control, again containing a Grid as the root element. As Silverlight is a browser plug in*, rather than the desktop application of WPF, this approach makes sense. However, I have to doubt the wisdom of this User Control being named "Page.xaml" by default, as this might cause confusion to those of us who have also created Pages in WPF Applications and think of Pages as being, well, er, pages!.
To add some elements to the User Control, you simply drag them from the Asset Library on to the design surface. I added a TextBlock, an Image and a Button.

If you are not familiar with Expression Blend, the Asset Library - where you drag your elements from - is the last icon on the left, which I have circled in Red. I added the png file which is used as the Source property of the Image to the project files.
A Button that does nothing when clicked won't impress anyone, so my next task was to fix this. My inclination was to switch straight away to Visual Studio, but I thought I would see what Blend had to offer in the way of event handler creation.
The Properties Pane in Blend has a button which causes the Properties to be listed (the default choice) and next to that there is a button for events. You can see this in the screenshot below.

Expression Blend is essentially a tool for creating visual user interfaces. So, unsurprisingly, all you get when you choose that Events pane button is a list of common events for the particular element or control. What is quite impressive though is that if you type in the proposed name for the event handler (as I have done in the example below) and then hit the Enter key, not only does this cause Visual Studio to fire up, but when you look at the code-behind page you will see that a handler of the name you input will be created and it will have the correct signature. You can then simply insert whatever code for what you want to happen as a result of the button click.

One thing I was keen to try out was Silverlight's Visual State Manager, about which I had heard much, but understood nothing. As it seems certain that WPF will have VSM soon, I wondered how much easier (or not) VSM would be compared to the current WPF Storyboard and Triggers approach.
I experimented by adding a simple animation to a button, transforming its size when the mouse entered its area. (I will be writing a full article on Visual State Manager soon, which will include VSM for controls which have CommonStates built in for you in Blend - such as the Button - as well as VSM for those that don't, such as Images.)
The steps for the Button are as follows:
- Drag a Button from the Asset Library on to the drawing surface (Note to self: Must get used to calling this the 'Artboard').
- Right click on the button and choose 'Edit Control Parts (Template)'.
- Select 'Edit a Copy'.
- Rename the Key if you want to.
- Click OK.
If you now look at the States Pane :-
you will see that a standard set of States for a Button object are displayed for you. These are the four main CommonStates
- Normal
- MouseOver
- Pressed
- Disabled
and two FocusStates:
If you want the visual state to change when the Button is pressed, you add a Transition to the Pressed State in that pane. This is done by clicking on the small 'Arrow and plus symbol' icon to the right of the word 'Pressed' in the States Pane. When you left click on this icon you will see a context menu which shows combinations of state changes. Select the first one - '*>Pressed'. The area round the Artboard should now have a red border and the "State recording is on" message appears at the top left corner. (If for any reason it is not on, simply click the mouse over the dot to the left of the "State recording is off" message.)
As my initial experiment, I wanted to reduce the width and height of the Button by 15% when it is pressed. Here are the steps:
- Select the part of the Button that you want to animate.
- Go to the Properties pane.
- Scroll down until you reach the 'Transform' section.
- Select the Scale transform icon (the arrow coming out of a rectangle).
- Change the value of X to 0.85.
- Change the value of Y to 0.85.
- Press F5 to test the project.
- Click on the button to ensure that the animation works.
- Close the Browser.
- In the Objects and Timeline pane in Blend, click on the 'Return Scope to [UserControl]' icon at the right hand side of the Style name.
I had quite a lot of trouble with this. I found that if I clicked on the button on the Artboard and made the transformations (or any other animations, for that matter), I was not getting the results I expected. I eventually worked out that the trick is to select the exact element you want to animate from the Objects and Timeline pane. In the case of the button transformation, the item to select is the parent grid. In some cases, you may need to expand the dropdown list to get to the exact item you need and you may have to experiment some before you get it right. I think this kind of problem is only likely to occur where you stick with the standard template; clearly, if you build up your own set of elements on the Artboard to make a styled button of your own, you will be more familiar with exactly which part(s) you want to animate.
Another little thing to watch out for which might confuse at first, is that the States pane seems to 'remember' the last set of States you dealt with. If you then select a different object, (for example, the TextBlock in my initial user Control) and the States haven't been changed or set for this object, you will still see the States from the preceding object in the pane.
Because I was familiar with the Triggers and Storyboard approach, I did want to see if this was still available as an option in Blend. It doesn't seem to be, but of course I can always do this in XAML in Visual Studio, using an EventTrigger. However, I can see that the Visual State Manager is going to be an easy beast to tame, so I don't think I'll need to revert to the old way very often.
So at this stage, my first impression is that I will be content to use Blend for complex graphics and animations, gradients and visual states. For most other things, I think I will still be using Visual Studio whenever possible.
* At least it is a browser plug in for the time being. Silverlight 3, which is coming up fast, is going to offer an out-of-browser experience. That is, you will be able to include code that will let the user access the Silverlight User Control when offline. My first thought was "Isn't that what WPF does?", but then realized that Silverlight is cross-browser, cross-platform whereas WPF is not. It will be interesting to see where this leads in the long term.
I've always found ListViews quite fascinating. Slightly confusing sometimes, but fascinating nevertheless.
As I have often been heard to say, it's the little Gotchas that'll get ya. Take, for instance, the subject of this blog - copying an item between ListViews. The scenario is that you let the user click on an item in ListView1 and if they want this item copied to ListView2, they hit a button.
Now, you would probably think that all you need to do is identify the currently selected item and add it straight to the second ListView. Something like:
Code Copy
ListView2.Items.Add(ListView1.SelectedItems(0))
But if you do try this, you will get an error.

The text of the error message pretty much says it all. What you have to do (assuming that you aren't prepared to remove the item from the original ListView) is to clone it. You will then be allowed to add the clone to the second ListView.
Although the cloning isn't difficult, you do have to be aware of the need to cast the selected item to ListViewItem if you have Option Strict On. To be honest, I found this a bit strange at first. If I lift a ListViewItem from a ListView, I didn't expect to have to cast it to what it is - i.e a ListViewItem.
I'm not entirely sure why this occurs and wonder if the underlying reason for this is that the ListViewItem is stored in the SelectedItems collection of the ListView as a generic object. Anyway, Casting it back to a ListViewItem at the point where the cloning takes place, fixes this without any problem.
This code works well:
If ListView1.SelectedItems.Count > 0 Then
Dim lvi As New ListViewItem
lvi = ListView1.SelectedItems(0)
Dim lvi2 As New ListViewItem
lvi2 = CType(lvi.Clone, ListViewItem)
ListView2.Items.Add(lvi2)
End If
You'll have noticed that I built in a test to ensure that an item is currently selected. It's an easy thing to forget and is sure to bring your app to a grinding halt before long if you don't build this in.
You can see where I have cast the selected item (aka lvi) to ListViewItem in Line 5. Intriguingly, casting to ListViewItem in the third line of code, e.g.
Code Copy
lvi = CType(ListView1.SelectedItems(0), ListViewItem)
doesn't cut it.
Sometimes you don't have control over how the data is saved to a text file. For instance, some items might be saved with quotation marks around words or phrases. If you want to read the file but not show these marks then you'll need a way to remove them.
Like a lot of things, it's actually very easy when you know how. You can use the built-in Replace function of the String class, replacing the marks as you find them. The trick though (and to my mind, the less than totally intuitive bit) is knowing how many quotation marks to use in the first argument of the Replace method's parameters. This is the 'OldChar' parameter, i.e the one you want to replace.
You would think, wouldn't you, that you could put a Quotation Mark inside a pair of Quotation Marks like this:-
MyString.Replace(""", "")
But if you try that, you will find that it doesn't work. What you actually have to do is include a second Quotation Mark inside the outside ones. In other words, you need four Quotation Marks in a row.
MyString.Replace("""", "")
It's only a tiny change, but it will move your mental state from annoyed confusion to enlightened contentment. Or something like that, anyway.
So putting this together with code that reads from a file and displays the result (minus Quotation Marks) in a ListBox, you have:
Private Sub RemoveQuotes(ByVal filename As String, ByVal target As ListBox)
' A StreamReader to fetch the data
Dim sr As New IO.StreamReader(filename)
' A string to hold each line as it is read
Dim line As String = String.Empty
' Read from the file
' As long as there is something left to read
Do While sr.Peek <> -1
' Replace the Quotation Marks with Nothing
line = sr.ReadLine.Replace("""", "")
' Add edited text to a ListBox
target.Items.Add(line)
Loop
' Tidy up when finished
sr.Close()
sr = Nothing
End Sub
If you prefer your code to be broken down into clearer steps, you could do this instead:
Private Sub RemoveQuotes(ByVal filename As String, ByVal target As ListBox)
' A StreamReader to fetch the data
Dim sr As New IO.StreamReader(filename)
' A string to hold each line as it is read
Dim line As String = String.Empty
' Read from the file
' As long as there is something left to read
Do While sr.Peek <> -1
' Read the next line
line = sr.ReadLine
' Replace the Quotation Marks with Nothing
line = line.Replace("""", "")
' Add edited text to a ListBox
target.Items.Add(line)
Loop
' Tidy up when finished
sr.Close()
sr = Nothing
End Sub
Either way, your quotation marks will be history.
One of the things that first caught me out in WPF was the simple topic of colors. For example, let's say you want to reset the BackColor of a Form in Windows Forms.
Easy enough. This will do the job:
Code Copy
Me.BackColor = Color.CadetBlue
When it comes to WPF, you'll know that we are dealing with a Window, instead of a Form and have probably already picked up that BackColor is now Background. You can however, still use "Me" to reference the Window.
But if you were to try something like:
Code Copy
Me.Background = Color.CadetBlue
' or even
Me.Background = Colors.CadetBlue
you would be disappointed.
You would however get some help from Intellisense (at least with the second version). The error message tells you that a Color cannot be converted to a Brush. And there's the answer to the problem.
The Background property doesn't take a Color - it takes a Brush, which of course can, and usually does, have a color assigned to it. Don't forget though that you are not limited to a single solid color; there are many gradient, tile and image based options that you can choose when it comes to brushes in WPF.
So this code will work fine in WPF:
Code Copy
Me.Background = New SolidColorBrush((Colors.CadetBlue))
Ah yes, I hear you say, but what about the theory that you should use XAML for the look and code-behind for the behaviour? Well, I can't disagree with you there and personally I would use:
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Background="CadetBlue">
where the Background property for the Window is set there in the XAML. However, there may well be times when you want the user to have a say in color choices and in those cases it can be easier to take the user's input and deal with it in the code-behind.
For example, if the user was empowered to enter values for the ARGB components then you might use an approach like the following:
Code Copy
Dim col As New System.Windows.Media.Color
' In reality the values below could be
' selected by the user and passed in
col = Color.FromArgb(214, 122, 52, 24)
Dim br As New SolidColorBrush(col)
Me.Background = br
It would also be quite easy to create a display in WPF where you bind, for example, sliders to the Brush that is used for the background. But I won't go any deeper into that just now, as this sub-set of blog items is meant only to help identify those missing WinForms favorites and repatriate them as WPF troops.
This is a question that seems to come up a lot in the forums:- How can I restrict the TextBox input to numerals, or only a single occurrence of a decimal point, or some other restriction?
As ever, there are several approaches. If the restriction is something basic, such as numerals only then the easy approach is to use the KeyPress event. What you can do is stop the character from appearing in the TextBox, test to see if it is allowable and, if it is, then allow it to continue.
Letters Only
To take an example which only allows letters of the alphabet, it would look like this:
Private Sub TextBox1_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBox1.KeyPress
e.Handled = True
If e.KeyChar Like "[A-z]" Then e.Handled = False
End Sub
In this snippet, it is the e.Handled = True which blocks the input temporarily. The next line assesses whether the key press is a letter of the alphabet*, either lower or upper case, and if it is then the handled setting is reversed. This allows the key press to be passed to the TextBox display. If it fails the test, the block on this key press remains.
* Depending on your locale and keyboard, some other keys are allowed. These include symbols that are used in combination with characters in some languages, such as accents. In most cases this is the behaviour you will want.
Specific Keys
Sometimes you may want to allow certain keys. A common situation is where you will let the user use the Backspace to correct an error when inputting:
Private Sub TextBox2_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBox2.KeyPress
e.Handled = True
If e.KeyChar Like "[A-z]" _
Or e.KeyChar = Chr(&H8) Then
e.Handled = False
End If
End Sub
In this case, it is the Chr(&H8) which identifies and allows the Backspace.
Numbers Only
Another common requirement is to restrict input to numerals. Of the several possible approaches, using IsNumeric is one of the most straightforward:
Private Sub TextBox3_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBox3.KeyPress
e.Handled = True
If IsNumeric(e.KeyChar) Then e.Handled = False
End Sub
Sometimes that is too restrictive though. What happens if you want to allow the user to enter decimal points or (depending on their locale) commas to break up large numbers? Allowing these individual characters is simple, but there is another potential catch as we will see in a moment:
Private Sub TextBox4_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBox4.KeyPress
e.Handled = True
If IsNumeric(e.KeyChar) _
Or e.KeyChar = "." _
Or e.KeyChar = "," Then
e.Handled = False
End If
End Sub
Only One Decimal Point
In most cases where users are inputting numeric values you will want to restrict them to a single decimal point. The code above will allow multiple entries. Again, there are several solutions, but the following one will usually do the job:
Private Sub TextBox5_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBox5.KeyPress
e.Handled = True
If e.KeyChar <> "." Then
If IsNumeric(e.KeyChar) Then e.Handled = False '
ElseIf TextBox5.Text.Contains(".") Then
MessageBox.Show("Only one decimal point allowed")
Else
e.Handled = False
End If
End Sub
Command Keys
If you use any of the previous methods, you will be able to control the standard input keys. But there is another group of keys - Command keys - which won't be excluded by the use of the e.Handled approach. These include such keys as Home, End, Tab, and so on. You may risk alienating your users by excluding these, but there may be times when it is reasonable to do so, in which case you'll need to know how.
A good way is to intercept the message at the window level and you can do this by overriding the ProcessCmdKey function. Here's how:
Create a new class which inherits from the basic TextBox. Override the ProcessCmdKey function and test for the currently pressed key in a similar way to that used in the earlier examples. If the key is one you want to suppress then you return True and the Windows message pump will ignore it.
The following code will be all you need:
Public Class CustomTextBox
Inherits System.Windows.Forms.TextBox
Sub New()
Me.BackColor = Color.Azure
End Sub
Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, ByVal keyData As System.Windows.Forms.Keys) As Boolean
' Declare a variable of type Keys enumeration
' named keyPressed.
' Cast the msg's WParam as a KeyEnum value
' and assign it to the keyPressed variable.
Dim keyPressed As Keys = CType(msg.WParam.ToInt32(), Keys)
' Process the key that is pressed.
' If keyPressed = Keys.Home Or keyPressed = Keys.End Then Return True
If keyPressed = Keys.Tab Then Return True
' Return the Command key message
Return MyBase.ProcessCmdKey(msg, keyData)
End Function
End Class
The light blue back color is simply to make this sub-classed TextBox look slightly different from the default one for demo purposes, but of course is not a key part of the key checking functionality. As you can see, my example blocks the Tab key. You can add or replace other keys, such as Home and End.
Multiple Options
Handling the KeyPress is fine if you only have a few TextBoxes for which you are controlling input. If there are going to be a lot of them throughout your application, or if you have different input rules for several TextBoxes, then again it may be worth your while to create your own inherited version.
The following example deals with some of the previous scenarios, but allows the input rule to be selected from an enumeration of choices. The choices used here remain basic, but of course you can expand this idea much further.
Here is the code:
Public Class RestrictedTextBox
Inherits System.Windows.Forms.TextBox
Enum RestrictionCategory
NoRestriction
NumeralsOnly
LettersOnly
AlphanumericOnly
End Enum
Private _allowedKeys As RestrictionCategory
Property AllowedKeys() As RestrictionCategory
Get
Return _allowedKeys
End Get
Set(ByVal Value As RestrictionCategory)
Select Case Value
Case 1 To 3 ' One of the enum choices
_allowedKeys = Value
Case Else ' No restriction
_allowedKeys = 0
End Select
End Set
End Property
Protected Overrides Sub OnKeyPress(ByVal e As KeyPressEventArgs)
MyBase.OnKeyPress(e)
' Test whether key is allowed, based on the current choice
' from the enum
Select Case _allowedKeys
Case 1 'Numerals only
If IsNumeric(e.KeyChar) Then
Exit Sub
Else
e.Handled = True
End If
Case 2 ' Letters Only
If e.KeyChar Like "[A-z]" Then
Exit Sub
Else
e.Handled = True
End If
Case 3 ' Alphanumeric
If e.KeyChar Like "[A-z]" _
Or IsNumeric(e.KeyChar) Then
Exit Sub
Else
e.Handled = True
End If
End Select
End Sub
End Class
The key areas are the enumeration which is called RestrictionCategory. These are automatically assigned values from 0 to 3. The Property AllowedKeys and its backing Field carry out the standard roles of a Property, the user being able to set the AllowedKeys property in code. (You could improve this by having the property appear in the Properties Window).
The core of this class is the overridden OnKeyPress method. This checks for the chosen enumeration and then either allows or applies the blocking filter to the currently pressed key. This works in a very similar way to the individual KeyPress approach used in the earlier examples.
By default, all keys will be allowed and to set the enumeration of your choice, you simply include code similar to the following somewhere appropriate in your form (I've used the Form Load event for my example):
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Me.RestrictedTextBox1.AllowedKeys = RestrictedTextBox.RestrictionCategory.NumeralsOnly
End Sub
Summary
I think that a combination or extension of any of the above approaches will enable you to control exactly what you will allow the user to input into a TextBox.
My blog has moved to it's new address.
You can find my new blog here.
Another 'old' bit of code that I'm still using a lot none the less. Whenever I needed drag and drop, it annoyed me that I had to use repetitive code to start it manually by tracing the mousedown etc. So what better place for repetitive code than in a reusable component. It isn't any highly fancy or complicated code, but a raincoat doesn't have to be pretty to keep you dry ;)
Anyway, when I was thinking when extending the AutoDragger today to alter the behaviour when dragging from a datagridview, that it was never posted on this blog. So here it is. (As far as I know it hasn't been outdated in the sense that .net now supports this out of the box)
Usage in Designer: drop the component on your form, select a control you wish to enable auto dragging for, locate the added 'UseAutoDrag' property on that control (under a 'Drag Drop' header) and set it to true.
(Of course the component can be used as a runtime class too.) The cursor can be set in the drag events or by setting a default drag cursor on the component.
namespace Subro.Controls
{
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Drawing;
///
<summary>
/// Generic component that can start dragging for most control by handling
/// its mouse events.
/// Set the Controls property to indicate for which controls dragging should be handled
/// automatically. Catch the StartDrag event to alter the data which is to be dragged.
/// </summary>[
DefaultEvent(
"StartDrag")]
[
ProvideProperty(
"UseAutoDrag",
typeof(
Control))]
public class AutoDragger :
Component,
IExtenderProvider{
public AutoDragger()
{
}
public AutoDragger(
IContainer c)
: this()
{
c.Add(this);
}
public AutoDragger(
Control ctr)
: this(ctr, null)
{
}
public AutoDragger(
Control ctr,
EventHandler<
AutoDragEventArgs> handler)
: this()
{
Register(ctr);
if (handler !=
null)
StartDrag += handler;
}
List<Control> controls = new List<Control>();
public
int Register(
Control DragSource)
{
int i = controls.IndexOf(DragSource);
if (i != -1)
return i;
if (!DesignMode)
{
DragSource.MouseDown += new MouseEventHandler(Control_MouseDown);
DragSource.MouseMove += new MouseEventHandler(Control_MouseMove);
DragSource.MouseUp += new MouseEventHandler(Control_MouseUp);
DragSource.GiveFeedback += new GiveFeedbackEventHandler(Control_GiveFeedback);
}
controls.Add(DragSource);
i = controls.Count - 1;
ControlAdded(DragSource);
OnControlsChanged();            
return i;
}
protected virtual void ControlAdded(Control c)
{
}
public
void UnRegister(
Control DragSource)
{
DragSource.MouseDown -= new MouseEventHandler(Control_MouseDown);
DragSource.MouseMove -= new MouseEventHandler(Control_MouseMove);
DragSource.MouseUp -= new MouseEventHandler(Control_MouseUp);
DragSource.GiveFeedback -= new GiveFeedbackEventHandler(Control_GiveFeedback);
controls.Remove(DragSource);
ControlRemoved(DragSource);
OnControlsChanged();
}
protected virtual void ControlRemoved(Control c)
{
}
public
void UnRegisterAll()
{
int c = controls.Count -1;
for(
int i  = c;i>=0 ;i--)
UnRegister(controls[i]);
}const string Category = "DragDrop"
[
DefaultValue(
null)]
[
Category(Category)]
public Control[] Controls
{
get
{
return controls.ToArray();
}
set
{suspendcontrolschanged =
true;
while (controls.Count > 0)
UnRegister(controls[0]);
if (
value !=
null)
foreach (
Control c
in value)
{
Register(c);
}
suspendcontrolschanged = false;
OnControlsChanged(); } }bool suspendcontrolschanged;
protected virtual void OnControlsChanged()
{
if (!suspendcontrolschanged && ControlsChanged != null) ControlsChanged(this, EventArgs.Empty);
}public event EventHandler ControlsChanged;
public
Control this[
int Index]
{
get
{
return controls[Index];
} }///
<summary>
/// The amount of registered controls used as a dragsource
/// </summary>
public int DragSourceCount
{
get { return controls.Count; }
}public event EventHandler<AutoDragEventArgs> StartDrag;
private
DragDropEffects effects =
DragDropEffects.All;
[
DefaultValue(
DragDropEffects.All)]
public DragDropEffects Effects
{
get { return effects; }
set { effects = value; }
}protected
override void Dispose(
bool disposing)
{
UnRegisterAll();
base.Dispose(disposing);
}
int diffmin = 2;
[
DefaultValue(2)]
[
Category(Category)]
public int DifferenceMinimum
{
get {
return diffmin; }
set{
diffmin = value;
}
}
AutoDragEventArgs curdrag;
void Control_MouseDown(
object sender,
MouseEventArgs e)
{
if (e.Button ==
MouseButtons.Left)
{
curdrag = new AutoDragEventArgs(sender as Control, e.Location, this);
}
elsecurdrag = null;
}void Control_MouseUp(
object sender,
MouseEventArgs e)
{
if (curdrag !=
null)
{
AutoDragEventArgs de = curdrag;
EndCurDrag();          
OnDragEnded(de);                    
} }protected virtual void OnDragEnded(AutoDragEventArgs e)
{
}
void Control_MouseMove(
object sender,
MouseEventArgs e)
{
if (e.Button ==
MouseButtons.Left && curdrag !=
null)
{
curdrag.CheckStartDrag(e);
}
}
void Control_GiveFeedback(
object sender,
GiveFeedbackEventArgs e)
{
curdrag.SetFeedback(e);
}
private
Cursor cursor;
[
DefaultValue(
null)]
public Cursor DragCursor
{
get {
return cursor; }
set{
if (cursor == value) return;
cursor = value;
dragicon = null;
} }
bool ShouldSerializeDragCursor()
{return cursor != null && dragicon == null;
}private
Icon dragicon;
[
DefaultValue(
null)]
public Icon DragIcon
{
get {
return dragicon; }
set{
if (dragicon ==
value)
return;
if (
value ==
null)
DragCursor = null;
else
DragCursor = new Cursor(value.Handle);
dragicon =
value;
} }private
bool alwaysshowcustomcursor;
[
Description(
"Only applies when the DragCursor property is set. If this value is false, the cursor is only shown when drop is allowed")]
[
DefaultValue(
false)]
public bool AlwaysShowCustomCuror
{
get { return alwaysshowcustomcursor; }
set { alwaysshowcustomcursor = value; }
}
void EndCurDrag()
{
if (curdrag !=
null)
curdrag.Dispose();
}public
class AutoDragEventArgs :
EventArgs{
public
readonly Control Control;
public readonly Point StartPoint;
public readonly AutoDragger AutoDragger;
public AutoDragEventArgs(
Control Control,
Point StartPoint,
AutoDragger Owner)
{
this.Control = Control;
this.StartPoint = StartPoint;
this.AutoDragger = Owner;
}private
object dragobj;
public object DragObject
{
get { return dragobj; }
set { dragobj = value; }
}            private bool started;
public
bool DragStarted
{
get { return started; }                
}
internal
void CheckStartDrag(
MouseEventArgs e)
{
int diff =
Math.Abs(e.X - StartPoint.X)
+ Math.Abs(e.Y - StartPoint.Y);
if (diff >= AutoDragger.diffmin)
{
started = true;
Cursor = AutoDragger.DragCursor;
AutoDragger.OnStartDrag(this,e);
}
}public
void Dispose()
{
if (AutoDragger.curdrag ==
this)
AutoDragger.curdrag = null;
}
private Cursor cursor;public
Cursor Cursor
{
get {
return cursor; }
set{
cursor = value;                    
}
}internal
void SetFeedback(
GiveFeedbackEventArgs e)
{
if (cursor !=
null)
{
if (e.Effect !=
DragDropEffects.None || AutoDragger.alwaysshowcustomcursor)
{
e.UseDefaultCursors = false;
Cursor.Current = cursor;
}
} }internal
object GetDragObject()
{
Control c = Control;
if (c
is ListControl)
return (c as ListControl).SelectedValue;
if (c
is TextBoxBase)
return GetDragObject(c as TextBoxBase);
if (c
is Label)
return c.Text;
if (c
is TreeView)
return GetDragObject(c as TreeView);
if (c
is DataGridView)
return GetDragObject(c as DataGridView);
return null;
}object GetDragObject(
TextBoxBase tb)
{
if (!tb.ReadOnly)
{
//when in edit mode and selecting text, don't start dragging
//TODO: make optional    
return null;
}
if (tb.SelectionLength == 0)return tb.Text;
return tb.SelectedText; }object GetDragObject(
TreeView t)
{
TreeNode node = t.GetNodeAt(StartPoint);
if (node == null) return null;
t.SelectedNode = node;
return node;
}object GetDragObject(
DataGridView dg)
{
DataGridView.
HitTestInfo ht = dg.HitTest(StartPoint.X, StartPoint.Y);
if (ht.RowIndex == -1)
return null;
if (ht.ColumnIndex > -1 && !dg.Columns[ht.ColumnIndex].ReadOnly && dg[ht.ColumnIndex, ht.RowIndex].IsInEditMode)
return null; //when on a cell in edit mode, don't start drag drop
if (ht.ColumnIndex == -1 || dg.SelectionMode ==
DataGridViewSelectionMode.FullRowSelect || AutoDragger.AlwaysDragFullRow)
{
DataGridViewRow row = dg.Rows[ht.RowIndex];
if (row.DataBoundItem != null) return row.DataBoundItem;
return row;
}
return dg[ht.ColumnIndex, ht.RowIndex].Value; }}
[
Browsable(
false)]
public AutoDragEventArgs LastDragInfo
{
}protected
virtual void OnStartDrag(
AutoDragEventArgs e,
MouseEventArgs me)
{
if (StartDrag !=
null)
StartDrag(this, e);
if (e.DragObject ==
null)
e.DragObject = GetDragObject(e);
if (e.DragObject !=
null)
{
e.Control.DoDragDrop(e.DragObject, Effects);
OnDragStarted(e);
}
else{
//dragging not allowed
curdrag = null;
} }protected virtual void OnDragStarted(AutoDragEventArgs e)
{
}
protected
virtual object GetDragObject(
AutoDragEventArgs e)
{
return e.GetDragObject();
}   
private
bool fullrow;
[
DefaultValue(
false)]
[
Description(
"Only applies when dragging on a datagridview.\r\nIf this value is not set, the full row only will be dragged if the selection mode of the grid is FullRowSelect or the rowheader is dragged and the cell value otherwise.")]
public bool AlwaysDragFullRow
{
get { return fullrow; }
set { fullrow = value; }
}
#region IExtenderProvider Members
bool
IExtenderProvider.CanExtend(
object extendee)
{
return extendee is Control;
}[
DefaultValue(
false)]
[
Category(Category)]
[
DesignerSerializationVisibility(
DesignerSerializationVisibility.Hidden)]
//serialized in the control property
public bool GetUseAutoDrag(
Control c)
{
if (controls.Count == 0) return false;
return controls.Contains(c);
}
public void SetUseAutoDrag(Control c, bool value)
{if (GetUseAutoDrag(c) == value)
return;
if (!value)
UnRegister(c);
else
Register(c);
}
#endregion
}
} . . .
I've been meaning to blog about the tools I use regularly and find useful and I've managed to get a couple written this month. I'm always a bit concerned that blogs like this might end up looking like some kind of infomercial. But I eventually came to the conclusion that I've always thought that personal recommendation was the best kind of recommendation, so if I like a tool I should tell the world. Whether the world sets any store by my opinion is a matter for the world to decide! J

I have been using Camtasia Studio on and off for a couple of years now. When I first got hold of a copy, I needed it to create some demo videos. What I really liked about it was that, with no previous experience of screen recording, I was able to make the recording successfully after only a couple of tries. OK, so when I look back at that effort now, maybe "successfully" is an extravagant claim, but it was good enough for what I wanted to do at the time.
Although there are still some (many!) features that I haven't yet got round to using, it has still been a really useful tool for me. When I first got it, I was more interested in grabbing the screen action and recording some audio narration. More recently, I needed to improve the narration side and so was looking for a way to record the voice independently and (often) edit parts of it without having to re-record the whole thing for the umpteenth time.

Camtasia Studio comes with a subset of handy little apps, one of which is their Audio Editor. This has been really useful, as it enabled me to make those voice recordings and edit them easily. I also found myself doing a lot of "Inserting Silence" to replace the intakes of breath and the occasional unwanted clunk when I'm waving my hands around and hit the boom mike.
The tutorials are excellent and there is a lively set of User forums. FAQs are available to help you with those tricky things that should go right but somehow don't.
The work area is as easy to use as you could reasonably hope for with an application that seems to have so many options. As you can see from the screenshot, the timeline allows for more than one audio track.
Version 6 of Camtasia comes with even more presets to help you create the best screen size, resolution and (often important for web distribution or viewing) file size.
I'm currently working on creating some courses, all of which contain video demos. As I get more used to using it, I am of course finding that I can get from starting idea to finished production much quicker. When I've finished my current project, as a long term plan, I hope to be able to create a set of short videos to be made available for free here on VBCity.
All in all, I'm really pleased with it and many a time I've silently thanked the friend who first put me on to it. You can download a free 30 Day trial version from the TechSmith site here.
I now have several WPF books. I've written reviews on some of them and often mentioned them in answers to Forum questions from members who want to start out in WPF.
When I wrote those articles and answers, my opinion was mostly based on my experience as a general reader who was interested in seeing what WPF had to offer and dabbling with it at a fairly elementary level. For the past three months though, I've been involved with WPF on a daily basis in a training capacity. During this time, it's become clear that I turn to some of the books much more regularly than others - and to some of them only when fairly desperate).
With the very clear understanding up front that this is my personal experience, opinion and preference - which may be completely different from anyone else's - here is my view on the various books.
The book I first turn to is :
Pro WPF with VB 2008: Windows Presentation Foundation with .NET 3.5
by Matthew MacDonald.
Comprehensive, detailed, clearly written, understandable code snippets, and downloadable code samples that work.
It's almost impossible to fault this book.
And of course, as the title says, the code behind samples are all in VB.NET - the only book I know of so far that is aimed specifically at VB developers.
However, if you are a C# developer then that won't impress you much. For you, Apress and Matthew MacDonald have produced the equivalent C# title - Pro WPF with VB 2008: Windows Presentation Foundation with .NET 3.0. The key differences obviously being the absence of "VB.NET" in the title and the slightly earlier Framework.
I do have this version too and find it very useful when I want to try and understand the C# approach to a concept that I understand in VB.
The next book I reach out for is usually
Windows Presentation Foundation Unleashed
by Adam Nathan.
The key value of this book is that it is packed with little hints, tips, Gotchas and information that will help you fight your way through the minefield that you encounter when first starting with WPF.
One negative for me is that the code samples are in C#. There are however many useful XAML samples.
The book is in full color. This makes it slightly more attractive, but isn't a key issue for me.
Compared to most other WPF books, it appears to be relatively slim. In spite of this, I have still found it to be a gold mine of really useful WPF nuggets.
Third in line is:
Programming WPF
by Chris Sells and Ian Griffiths.
Another C# book, but again contains a lot of XAML. Ian Griffiths was one of the first writer/speaker/presenter/trainers on WPF in general and XAML in particular, so there are some useful insights to be had here.
On the negative side, my impression is that the authors prefer to use code behind sometimes where I would have liked to see the XAML alternative.
Make sure you get the Second Edition if you decide to go for this book. The First Edition is inevitably way out of date now.
Surprisingly (to me anyway, considering I spent a lot of time converting the code in the next book from C# to VB) the Petzold book only comes fourth in my list of most used books. This is:
Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation
by Charles Petzold.
If you want intricate details and almost philosophical insight into the structure of WPF, then this would be the book for you. As with all Petzold books, it painstakingly works its way through the minutiae of the subject.
If I have a criticism, it would be that sometimes the sackcloth and ashes approach to developing (such as manually creating everything yourself, even if it is available for free) doesn't always work for me. I understand the purity of the approach, but this means that it can sometimes be difficult to just dip into this book for a quick answer to a particular question.
However, if you really - really - want to get at the nuts and bolts of WPF, then you will probably not find one that has more detail.
The fifth book in my WPF stable (for now, anyway!) is:
Professional WPF Programming: .NET Development with the Windows Presentation Foundation
by Chris Andrade and others. 
When there were very few other books available, this one had some benefits. The authors had each looked at specific areas and came up with some guidance. Some parts were clearly better than others, but overall it was an easier read than MSDN at the time, but basically rather shallow coverage.
It rarely gets opened these days.
I have the greatest respect for anyone who takes the time, trouble and effort to write or co-write a book. Knowing how long it takes me to write a short article or a training package, I wouldn't dream of criticising someone who will have spent many long, hard months writing (and no doubt re-writing) a book. So, as I said at the start, the opinions in this blog item only reflect my personal experience of using this collection of books; other readers with other backgrounds and author style preferences may well have different opinions.
As WPF picks up speed and interest grows, I'm sure there will be many other WPF books published in the coming months. There are already several that were published at the end of 2008 that I haven't read. No doubt the temptation to pick up another title or two will become irresistible as 2009 progresses and, if so, I will post up my thoughts on those too in the hope that it might help you decide the best way of spending your hard-earned cash.
Some weeks ago I wrote a blog item about how to get the functionality of PerformClick in WPF.
I remember thinking at the time that it was a fairly clunky workaround and wondered why PerformClick hadn't been included in WPF. Of course there may be lots of reasons, beginning with 'No-one thought of it' and ending with 'It was just too difficult'.
But while looking at an apparently unrelated problem this week, it occurred to me that maybe there is a more elegant solution staring me in the face - WPF's Commands.
In many cases where PerformClick is required, the situation is that whatever action is wired up to the Button's Click event is often also of interest to other controls - perhaps a menu item, a keyboard shortcut or a user instigated selection of some kind or other elsewhere.
WPF Commands, if this is a new area for you, is a variation on the theme of Events. Whereas events are generally locked in to a specific action, a Command on the other hand is more along the lines of a loosely-coupled task that can be subscribed to from several places.
Of course,you may be sat there thinking that it doesn't take a coding genius to create code for events that can be re-used and accessed from several different points. The point is though that Commands are specifically designed for this purpose and they have a feature that I think makes them almost unique. (Can you be 'almost' unique, the literary critic part of my brain asks? Never mind, you know what I mean!)
Commands have the ability to turn themselves off in situations where it wouldn't be appropriate for them to be available - and back on again when it is. A common example is the Paste Command, where it doesn't make sense to have it available unless there is some data in the Clipboard.
Similarly, you might build in a test where the Exit command isn't allowed to function unless the user has taken some action, such as saving data. There are lots of similar examples.
WPF comes with a bundle of pre-built Commands for common tasks, including Copy, Cut, Paste, Find, Save, Close, Play, Zoom, Align and many more. Naturally, the designers of WPF don't know the exact actions you will want to link to each of the commands in every circumstance, so there is still some work for you to do. Commands are clever - but they're not magic!
Apart from the built in Commands, and on the basis that there are never enough of these kind of things, you can create Commands of your own relatively easily. You can set the bindings and the rules that decide when it may be able to execute and when not (as per the Paste example earlier).
All of which brings me back to my original thought. If you can create a Command that both a Button and (in the case of the Forum question that got me thinking about this) a Menu Item can bind to, then that really removes the need to worry about PerformClick. Command would be a neater and more sophisticated way of achieving the same effect.
If you want more info on Commands, here is a useful link to an MSDN Introduction to Commands article.
It's been a long time since making this class and don't believe I posted it yet, but since I'm going to use it in the DataGridViewGrouper (see previous post), thought I'd post it now.
Since it's “older” code, it's fully .net 2.0 compatible. It can be uses where ever an IComparer interface is used. (or the Compare function of the GenericComparer<> class for a Comparison<> call)
Usage example:
Array.Sort(YourArray, new GenericComparer());
There is a generic comparer class already in the framework, but it will fail on certain types. It's been a while, but I believe the .net native class doesn't support the Nullable struct and strong implementers of the generic IComparer<> interface. (The new Linq will fail if the object sorted on is not an IComparable)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.ComponentModel;
namespace Subro
{
///
<summary>
/// Comparer that tries to find the 'strongest' comparer for a type.
/// if the type implements a generic IComparable, that is used.
/// otherwise if it implements a normal IComparable, that is used.
/// If neither are implemented, the ToString versions are compared.
/// INullable structures are also supported.
/// This way, the DefaultComparer can compare any object types and can be used for sorting any source.
/// </summary>
/// <example>Array.Sort(YourArray,new GenericComparer());</example>
public class GenericComparer :
IComparer{
public GenericComparer()
{
}
public GenericComparer(
Type Type)
{
this.Type = Type;
}
Type type;
public Type Type
{
get
{
return type;
}
set
{
if (value == null) throw new ArgumentNullException();
type = value;
comp = null;
} }
Type targettype;
/// <summary>
/// normally the same as the type, but can be set to a different type
/// </summary>
public Type TargetType
{
get
{
if (targettype == null) return type;
return targettype;
}
set
{
if (TargetType == value) return;
targettype = value;
comp = null;
} }
IComparer comp;
IComparer GetGenericComparer(
Type From,
Type To)
{
while (To !=
typeof(
object))
{
if (
typeof(
IComparable<>).MakeGenericType(To).IsAssignableFrom(From))
return (IComparer)Activator.CreateInstance(typeof(StrongCompare<,>).MakeGenericType(From,To));
To = To.BaseType; }
return null; }
public
IComparer GetComparer(
Type From,
Type To)
{
var gen = GetGenericComparer(From,To);
if (gen !=
null)
return gen;
else if (
typeof(
IComparable).IsAssignableFrom(type))
{
return (IComparer)Activator.CreateInstance(typeof(NonGenericCompare<>).MakeGenericType(type));
}
else if (type.IsGenericType && typeof(Nullable<>) == type.GetGenericTypeDefinition())
{
var basetype = type.GetGenericArguments()[0];
return (
IComparer)
Activator.CreateInstance(
typeof(
NullableComparer<>).MakeGenericType(basetype),
GetComparer(basetype, To == From ? basetype : To));
}
return new StringComparer(); }
class
NullableComparer<T>:
IComparer
where T:struct
{
public
readonly IComparer BaseComparer;
public NullableComparer(
IComparer BaseComparer)
{
this.BaseComparer = BaseComparer;
}
object getval(
object o)
{
return ((Nullable<T>)o).Value;
}
public
int Compare(
object x,
object y)
{
return BaseComparer.Compare(getval(x), getval(y));
}
}
class
StrongCompare<F,T> :
IComparer
where F : IComparable<T>
{
public
int Compare(
object x,
object y)
{
return ((F)x).CompareTo((T)y);
} }
class
NonGenericCompare<T> :
IComparer
where T: IComparable
{
public
int Compare(
object x,
object y)
{
return ((T)x).CompareTo(y);
} }
class
StringComparer :
IComparer{
public
int Compare(
object x,
object y)
{
return x.ToString().CompareTo(y.ToString());
} }
public
bool Descending
{
get
{
return factor < 0;
}
set
{
factor = value ? -1 : 1;
} }
int factor = 1;
int compare(
object x,
object y)
{
if (x == y)
return 0;
if (x ==
null)
return -1;
if (y ==
null)
return 1;
if (type ==
null)
Type = x.GetType();
if (comp ==
null)
comp = GetComparer(type, TargetType);
return comp.Compare(x, y);
}
public
int Compare(
object x,
object y)
{
return factor * compare(x, y);
}
}
public class GenericComparer<T> : GenericComparer,IComparer<T>
{
public GenericComparer()
: base(typeof(T))
{ }
public
int Compare(T a, T b)
{
return base.Compare(a, b);
}
}
public
class PropertyDescriptorComparer :
GenericComparer{
public readonly PropertyDescriptor Prop;
public PropertyDescriptorComparer(
PropertyDescriptor Prop)
: this(Prop, true)
{
}
public PropertyDescriptorComparer(
PropertyDescriptor Prop,
bool Descending)
: base(Prop.PropertyType)
{
this.Prop = Prop;
this.Descending = Descending;
}
}
public
static partial class Extend{
public
static void Sort<T>(
this List<T> list,
Func<T, T,
int> Comparer)
{
list.Sort(new Comparison<T>(Comparer));
}
///
<summary>
/// Sort on a property or field
/// </summary>
public static void Sort<T, S>(
this List<T> list,
Func<T, S> Property)
{
var c = new Subro.GenericComparer<S>();
Sort(list, (t1, t2) => c.Compare(Property(t1), Property(t2)));
}
}
}
. . .
Complete Source Code: http://blogs.vbcity.com/hotdog/archive/2008/12/19/9225.aspx
Note: the code is now rewritten to be 2.0 compatible (the project was written in vs2008 with compatibility set on framework 2.0), but older designers may still have trouble compiling because of the use of newer syntax (such as automatic properties). All 'var' keywords should be replaced, but they tend to sneak in because they are so easy to use ;)
If the newer syntax is a problem for many of you, drop me a note and I'll try to remove all of those syntaxes too.
Example:
Assuming you have a form with a datagridview called dataGridView1, add this code (after setting its datasource):
var grouper = new Subro.Controls.DataGridViewGrouper(dataGridView1);
grouper.SetGroupOn("NameOfThePropertyToGroupOn");
. . .
Functionality:
The DataGridViewGrouper is a component that can be added in the forms designer or in runtime to add grouping to any existing (databound) DataGridView. Grouping can be done on existing properties of the underlying data or with a custom value function. The displayed data can be altered through the exposed Display event.
Grouped rows can be quickly selected by double clicking the group header, and the rows can be collapsed and expanded by the user.
Usage:
3 simple steps
- Add the component to the designer or create it in runtime
- Set its DataGridView property to any existing databound DataGridView
-In runtime set the GroupOn property or call any of the SetGroupOn methods to start grouping
Other settings include
-the sorting of the groups (asc,desc or none, using the lists original sources order as leading)
-if the count of rows has to be shown (the shown information can be altered by catching the Display event)
-if the property name on which is grouped has to be shown (can also be altered by the Display event)
-besides the options above, the shown group value can also be altered in the Display event.
Background:
For a while now I was facing a request to add grouping to an overview in a DataGridView. And as you may have noticed: there is no grouping functionality in the default System.Windows.Forms.DataGridView.
Now there are ways of course, by adding DataGridViewRows manually and creating the groups by adding rows, but that would screw up the underlying data binding and automatic buffering/formatting/filtering/sorting, etc. Wanted to keep the bound functionality intact, but still create group rows. The solution in itself is simply to create a wrapper source that includes those rows and the grouped rows underneath and asign that to the grid, leaving the original source unaltered, and override the grouping rows painting/editing. A theory simple enough in itself, of course the implementation was a bit more work, but still less difficult than expected.
The grouping source was created easily enough, especially with the great functionality of Linq. The trick was in seperating what the grid saw from the underlying functionality. By inheriting from BindingSource all tools were available for that job. The DataSource and DataMember properties are set by the original values of the same properties in the Grid, therefor exposing the proper 'Columns'. All adding and removing had to be done in the underlying datagrid source, so those values were overridden.
The DataGridViewGrouper component itself is assigned to a grid and the Source of the grid itself is set to the grouped source. When a group row has to be painted, it is handled by the component, the other rows follow the default behaviour. Editing for a grouprow is simply cancelled. If the original source is a BindingSource, the source's positioning is synchronized.
Note that the index of the datagridview row won't be the same as the index in the original source, so you shouldn't be getting an object out of the original source directly with the row index. Then again, you should never do that anyway ;) Use the Grid.Rows[position].DataBoundItem property instead.
Source Code
TODO:
* Now the component synchronizes navigation if the source is another BindingSource. Will look later to synchronize with any CurrencyManager.
* Adding code comments :p
Just to let anyone who might be interested know that my Blog has now moved to http://cs.vbcity.com/blogs/skullcrusher/default.aspx
When it comes to text handling in WPF, you have a massive range of tools available, some of which can offer you a level of granularity that was previously unachievable. I plan to write about some of these text tools, such as Spans, Runs and Glyphs in future blogs, but for today I want to look at something a little less deep.
Recently I needed to insert a carriage return in a block of text. The text was being created in the XAML markup, and I didn't particularly want to resort to code-behind for what is essentially a UI feature. So I had to do a bit of digging to find how I could create, for instance, this result:-

where, if I don't override it, the two words would be placed one after the other on one line because there is more than enough width for them both.
As you might expect, there are several ways of achieving this, but I think the easiest one is to use a LineBreak element. This snippet shows the key part of the code that creates and formats the Button's Content, which in this example is of course text only.
<Button Margin="12,45" >
<TextBlock> Click <LineBreak/> Me!</TextBlock>
</Button>
You might think it a bit strange when you see that XML type item stuck into the middle of a block of text, but this is perfectly valid syntax. Because the Content isn't a single block of text, it's necessary to use the TextBlock to house the text and the LineBreak instruction.
Actually, what I really wanted to do wasn't quite as plain as the above screenshot. It was more along these lines:-

The markup for this is still not particularly complex. It just requires more properties to be set specifically:
<Button Margin="12,45" Background="LightSkyBlue">
<Button.Content>
<TextBlock TextAlignment="Center" FontSize="16" FontFamily="Calibra"
FontWeight="Bold" Foreground="Navy" FontStyle="Italic" >
Click <LineBreak/> Me!</TextBlock>
</Button.Content>
</Button>
I did mention that there are several ways and these include using a StackPanel containing multiple TextBlocks or setting the Padding property on a single TextBlock. An alternative you might consider is to preserve the whitespace. This effectively means that however you enter the text layout in your XAML will be faithfully reproduced in the WPF display.
The following markup:
<Button Margin="12,40">
<TextBlock FontSize="14" VerticalAlignment="Center"
HorizontalAlignment="Center" xml:space="preserve">Click
Me!</TextBlock>
</Button>
will achieve a similar result:

However, I really don't think the way the Markup sits in the XAML markup window is particularly neat. If you're interested in experimenting, try moving the word "Me!" so that it is more neatly aligned. Or Start the word "Click" on a new line of its own, maybe aligned with the markup above it.
The results are unlikely to be what you wanted to achieve and this is because the preservation is absolute. A XAML twist on WYSIWYG!
However, I included it here because the space preserve approach will be very useful to you in those cases where you want more than a single space between words or characters. Simply type out the text with the exact spacing that you require, set the XML Space attribute to Preserve and your requirements will be honored.
<Button Margin="12,40">
<TextBlock FontSize="14" VerticalAlignment="Center"
HorizontalAlignment="Center"
xml:space="preserve">Name: Ged</TextBlock>
</Button>

So there are a couple of tips for dealing with text in WPF. There's more to come!
For several years, whenever I've needed to include code snippets in my blogs and articles, I've used a neat little application that VBCity member HotDog wrote. This has stood me in good stead until the day that XAML arrived on the scene. Unfortunately, XAML kinda confuses HotDog's AutoFormatter tool and I end up with missing angle brackets.
A bit of searching led me to a tool called CopySourceAsHTML. Although I had seen this used with Visual Studio 2005, there was a minor problem using it with 2008 for a while, but this was easily fixed with a minor tweak.
Now though there is an official Visual Studio 2008 version. The extra good news about this is that it will also copy XAML to HTML.
One thing to watch out for if you have used the tool before and usually right click on a code snippet to copy is as HTML:

you will expect to see the Copy As HTML option in the menu.
However, this doesn't appear in the context menu if you right click on the markup in the XAML Markup Pane. But it is available and all you have to do is go to the IDE main menu, select Edit and the Copy As HTML menu item will be available for you there.
This great little utility is written (and updated) by Colin Coller of J T Leigh and Associates. You can download it from here.
One of the things that often pulls me up in mid flow when I'm putting WPF UIs together in a Window is that some of the core properties seem to have disappeared. Now, some of these are so fundamental that I know they have to be there, but obviously hiding under a different name.
Just recently, this happened to me twice in a row and I ended up scrolling through the Properties in the Object Browser, getting dizzy with the whizzing list of choices until I finally found them. All I was looking for was a very simple thing in both cases. I just wanted to apply some text to a control.
The first one was the good old CheckBox. In Windows Forms this has a Text property, of course, and it's whatever textual information you want to place at the side of the little square check area. The WPF CheckBox will allow you to do a zillion clever things to change its look - add Borders, TextBlocks, Images, Panels, whatever you like - and at times I'm really happy about that. But as it happened this time I just wanted to plonk a couple of words of description at the side.
You'll have gathered that there isn't a Text property for the WPF CheckBox and after a bit of a search, I did find its replacement - the Content property. If you assign plain text to this property, WPF will honor your choice. So this:
17 <CheckBox x:Name="CheckBox1" Content="Click Here" />
will do the job nicely.
Subsequently I discovered that the Content property is in fact the default property of a CheckBox, so this too will work:
17 <CheckBox x:Name="CheckBox1">"Click Here"</CheckBox>
Of course if you do want something more exotic, then you still use the Content property - you just add whatever multiple layers of content you desire.
The other control for which I had to search for the same kind of thing was the Expander. Again I only wanted a property that would just tack some plain text to the side of the Expander's little arrow.
This time the answer is to use the Expander's Header property.
<Expander Header="See More Choices"></Expander>
You can assign plain text to this property, but again you have the option to, well, expand the Expander's Header to make it a little more graphically interesting.

using this XAML:
<Expander.Header>
<Border BorderBrush="Navy" BorderThickness="2" CornerRadius="6">
<TextBlock Margin="10,2,10,2"
Foreground="Navy">What do you want to do?</TextBlock>
</Border>
</Expander.Header>
Although Content is the default property, don't fall into the trap of trying to use it as:
20 <Expander> Click to view choices </Expander>
because that inserts the text as the first (and in this case, only) item in the area that you get to see when the Expander arrow is clicked. It will be the only item because Content isn't a container and you need to use something like a StackPanel if you have more than one child that you want to include in the expanded area.
Something like this:
<Expander.Content>
<StackPanel >
<CheckBox Margin="2,4" Content=" Do This"></CheckBox>
<CheckBox Margin="2,4" Content=" Do That"></CheckBox>
<CheckBox Margin="2,4" >
<CheckBox.Content>
<StackPanel Orientation="Horizontal">
<Image Width="16" Source="DCP_1997.jpg"></Image>
<TextBlock Text=" View Profile"></TextBlock>
</StackPanel>
</CheckBox.Content>
</CheckBox>
<TextBlock TextWrapping="Wrap" Margin="3,30,0,0"
Text="Select a task from the list above." />
</StackPanel>
</Expander.Content>
to produce this result:

VBCity will be looking very different soon, as we change to the new Community Server based model. In advance of that change, our blogs are moving to a new area.
From now on, you can view my latest blogs both here and at the new site here. Once the new site is fully up and running, all new blog items will be posted on the new area only.
All previous blogs from VBCity Bloggers will still be available and hopefully will continue to rank highly in search engine results.
This question comes up every so often in the forums and it's one of those things that you'd think should be easy, but actually isn't. Well, it isn't until you know how - like most things in developer land. The question is usually something along the lines of "How can I grab the icon from an MS Word file?" The application may not necessarily be MS Word, but the that's the general thrust of it.
The only approach I have successfully used in this kind of scenario is the SHGetFileInfo API*. This API will grab the icon from an application.
Here's the way I do it: First, Import Runtime.InteropServices into your Windows Form.
Imports System.Runtime.InteropServices
Next, the API code, which goes at the top of the form, outside of any procedures:
Private Structure SHFILEINFO
Public hIcon As IntPtr
Public iIcon As Integer
Public dwAttributes As Integer
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)> _
Public szDisplayName As String
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=80)> _
Public szTypeName As String
End Structure
Private Declare Auto Function SHGetFileInfo Lib "shell32.dll" _
(ByVal pszPath As String, _
ByVal dwFileAttributes As Integer, _
ByRef psfi As SHFILEINFO, _
ByVal cbFileInfo As Integer, _
ByVal uFlags As Integer) As IntPtr
Dim hImgSmall As IntPtr 'The handle to the system image list.
Private Const SHGFI_ICON As Long = &H100
Private Const SHGFI_SMALLICON As Long = &H1
Note the two Constants at the end there being declared as Long. You'll usually find you have to tweak old API code to fix these kind of lines if you have Option Strict On.
To demonstrate this API in action, here is how I've structured it:
- I have added a button (btnSelectFile) to the Form.
- When this button is clicked, an OpenFileDialog is created to allow the user to select a file.
- Once a file has been selected, the file name is passed to a function that will dig out the icon for that file type and pass it back.
- For demo purposes, the Form's default icon will be replaced with this selected one.
Here is the code in the function that grabs the icon:
Function GetIcon(ByVal filename As String) As Icon
Dim shinfo As SHFILEINFO
shinfo = New SHFILEINFO
'Get the small icon.
hImgSmall = SHGetFileInfo(filename, 0, shinfo, _
Marshal.SizeOf(shinfo), _
SHGFI_ICON Or SHGFI_SMALLICON)
Return System.Drawing.Icon.FromHandle(shinfo.hIcon)
End Function
And here is that function being called from a Button Click, the icon then being shown as the Form's icon:
Private Sub btnSelectFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSelectFile.Click
Dim ofd As New OpenFileDialog
If ofd.ShowDialog = Windows.Forms.DialogResult.OK Then Me.Icon = GetIcon(ofd.FileName)
End Sub
That's pretty much all there is to it, if you are happy to plonk the icon on the form like that. But in most cases you'll probably want something a bit more reusable. One easy way to store the grabbed icon is to put it in an ImageList.
Assuming that you are only going to deal with one icon at a time, you can do something like this:
Dim myIcons As New ImageList
myIcons.Images.Add(System.Drawing.Icon.FromHandle(shinfo.hIcon))
Now that you have that icon safely stored in the ImageList, you can access the ImageList in the usual way if you want to place the icon image elsewhere. And of course you can change the above code slightly to allow for more than one icon to be added to the ImageList and used as required.
Finally, if you need to switch the icon into Bitmap format you can use the handy ToBitmap method:
Dim bmp As Bitmap = System.Drawing.Icon.FromHandle(shinfo.hIcon).ToBitmap
It is possible then to save the Bitmap by using the Bitmap's Save method. However, I have found this to be a bit hit-and-miss, with it sometimes throwing a generic GDI+ error when I tried to recall it for use by means of the Image.FromFile method.
I hope that if you need to grab an image from an application that you find this article useful.
----------------------------------------------------------------------
* There is the much more hopeful sounding ExtractIcon API which I have used to extract the icon of a currently running VB application, but haven't managed to port the logic over to the kind of file selection scenario described here.
PerformClick is one of the many casualties of a move from Windows Forms to WPF. Of course, it's probably not something you would need to use often, but just in case you do then there is a way.
It's not a very intuitive way and - had it not been that I spotted the C# version of this trick in Adam Nathan's WPF book - I wouldn't be sat here writing this now. I'd still be searching endlessly to try and find the answer.
In order to achieve this in VB, you need to do the following:
Add a Reference to UIAutomationProvider in your Project.
Add an Imports statement:
Imports System.Windows.Automation.Peers
Add another Imports statement:
Imports System.Windows.Automation.Provider
Use the following code snippet:
Dim buttonPeer As New ButtonAutomationPeer(Button1)
Dim invokeProv As IInvokeProvider = TryCast(buttonPeer.GetPattern(PatternInterface.Invoke), IInvokeProvider)
invokeProv.Invoke()
The above code to be placed wherever you want to simulate the Button's Click. (Change the Button name as needed).
It did occur to me that this task might be a good candidate for a custom Command, using WPF's quite versatile Command abilities. However, I haven't got past the "wonder if that's a good idea?" stage. And to be honest, probably won't any time soon.
Whether you feel that it's necessary to go to the trouble of the Automation or the Command approach is of course entirely up to you. I have to admit to being tempted simply to move the action code out of the Button Click event and into a separate method that can be called from both the Button Click and other events. But then, I've never been much or a purist about these things.
My .Net Journal blog posts will be made to the new vbCity blog site from now on.
Here's the link to my new blog site: Mike McIntyre's .NET Journal
RSS feed is: http://cs.vbcity.com/blogs/mike-mcintyre/rss.aspx
It is a little longer than I expected to get the the project that goes along with this post about background colours in WPF but as they say better late ....
The project can be downloaded from here.
I got caught the other day with a "what the heck" moment.
I was trying to display "Mr & Mrs" as an Item in a ComboBox but it just was not happening.
I suppose that this would not have come to light if my computer was not slowing down when I opened the ItemCollection window in Visual Studio. It was so slow that I would type something then have to wait for a couple of seconds to show what I had typed. If I made a mistake I had to change it which would take more time, so I decided to just add the five combobox items in the XMAL itself.
As soon as I typed the item in, I got the squiggly blue line and the following error.

Which really threw me.
Being an old HTML boy from years past it finally hit me. XAML is a markup language and most, if not all, markup languages use the ampersand (&) for defining special characters and XML (which XAML is based on) is no different.
XAML has the same character entities as XML and one of these is for using the ampersand in markup.
Having refreshed my memory getting the desired result was an easy fix.

Which, when run, shows correctly in the ComboBox.

Continuing my look at features that seem to be Missing in Action (MIA) from Windows Forms, vbCity colleague Matt pointed out to me that the Anchor property seems to have disappeared.
I'm really pleased that this came up, because it gives me an opportunity to highlight just some of the massive flexibility you have in WPF Windows when it comes to layout.
So what does WPF have to offer that will work as well as Anchors do? Umm, that should probably read 'better than Anchors do', because let's face it, WinForms Anchoring doesn't always offer you the precise flexibility you want. But the short answer to the original question is: Lots!
I'm only going to cover a few examples here and then invite you to play with the ideas I have suggested. Using these, you can ensure that every control (or element, as we should properly call them in WPF) sits exactly where you tell it to, and stays anchored or not as the Window resizes, according to your wishes.
HorizontalAlignment, VerticalAlignment and Margins
These two properties are reasonably close to the Anchor properties in WinForms. HorizontalAlignment gives you the choice of aligning (read: Anchoring) to Left or Right. So you can see how they relate to Anchor Left and Anchor Right. However, WPF never does layout by halves, so you also have two more choices - Center and Stretch. I won't insult your intelligence by telling you what those additional settings do.
Here's an example that will anchor a button to the top right hand corner of a Grid.
<Button Margin="0,5,5,0" HorizontalAlignment="Right"
VerticalAlignment="Top" >Button</Button>
The result you will get is:

Notice that there is a 5 unit gap to the top and right of the button. I carefully used the word "unit" there because old habits always want me to say 'pixel'. It isn't necessarily 5 pixels gap; WPF thinks in terms of 'device independent units', but in many common scenarios you can still think pixels.
The two gaps are set by the Margin property. "0,5,5,0" translated to:
- 0 for the Left Margin
- 5 for the Top Margin
- 5 for the Right Margin
- 0 for the Bottom Margin
Go ahead and tweak those settings to see how changes affect the position.
If you try this markup, run the project and then resize the Window, that button will stick like glue to its position at the top right.
Obviously, if you don't want any gaps then you can dispense with the Margin settings. This version uses the Left and Bottom settings only.
<Button HorizontalAlignment="Left" VerticalAlignment="Bottom" >Button</Button>

Margins Only
If you want an element to expand (and reduce) as the containing Window or other container resizes then you can achieve this just by setting the Margin properties.
<Grid Background="Black">
<Button Margin="20,50" >Button</Button>
</Grid>
.png)
.png)
Alignment and Width
Another combination you can try is to use one of the alignments, together with hard coding the Width or Height of a control. Usually you won't want or need to fix a height or width, but there may be cases where this works for you. Try something like this:
<Grid Background="Black">
<Button VerticalAlignment="Bottom" Width="90">
Button</Button>
</Grid>

And in case you were wondering - yes, you can also add settings for the Margins too, so that you can position it, for example, a set distance from the Bottom edge.
<Grid Background="Black">
<Button VerticalAlignment="Bottom" Width="90"
Margin="0,0,0,15">
Button</Button>
</Grid>
I hope that by now you're starting to get the idea that WPF layout isn't going to miss the Anchor property any day soon! Combine the techniques shown above and you have a wide range of options.
Canvas
But we're not done here yet. If you want to contain an element inside a panel and be able to fix its relative position, you could use a Canvas. Check out this markup:
<Grid Background="Black">
<Canvas Margin="50,35" Background="Yellow">
<Button Canvas.Left="12" Canvas.Top="3">Button</Button>
</Canvas>
</Grid>
This will net you the following, rather garish, result:

That button will remain 12 units from the left and 3 units from the top of that Canvas, no matter what else happens by way of resizing the Canvas, the Grid or even the Window.
I think that even these simple, single examples show a great deal of power and flexibility for positioning elements. Bear in mind that you can build up layers of containers and panels and elements inside each other - and control the placement of each of them individually in relation to its container.
While the familiar Anchor may be missing, I somehow think that once you become used to using these new techniques you won't actually miss it at all.
One of the things that first caught me out in WPF was the simple topic of colors. For example, let's say you want to reset the BackColor of a Form in Windows Forms.
Easy enough. This will do the job:
Me.BackColor = Color.CadetBlue
When it comes to WPF, you'll know that we are dealing with a Window, instead of a Form and have probably already picked up that BackColor is now Background. You can however, still use "Me" to reference the Window.
But if you were to try something like:
Me.Background = Color.CadetBlue
' or even
Me.Background = Colors.CadetBlue
you would be disappointed.
You would however get some help from Intellisense (at least with the second version). The error message tells you that a Color cannot be converted to a Brush. And there's the answer to the problem.
The Background property doesn't take a Color - it takes a Brush, which of course can, and usually does, have a color assigned to it. Don't forget though that you are not limited to a single solid color; there are many gradient, tile and image based options that you can choose when it comes to brushes in WPF.
So this code will work fine in WPF:
Me.Background = New SolidColorBrush((Colors.CadetBlue))
Ah yes, I hear you say, but what about the theory that you should use XAML for the look and code-behind for the behaviour? Well, I can't disagree with you there and personally I would use:
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Background="CadetBlue">
where the Background property for the Window is set there in the XAML. However, there may well be times when you want the user to have a say in color choices and in those cases it can be easier to take the user's input and deal with it in the code-behind.
For example, if the user was empowered to enter values for the ARGB components then you might use an approach like the following:
Dim col As New System.Windows.Media.Color
' In reality the values below could be
' selected by the user and passed in
col = Color.FromArgb(214, 122, 52, 24)
Dim br As New SolidColorBrush(col)
Me.Background = br
It would also be quite easy to create a display in WPF where you bind, for example, sliders to the Brush that is used for the background. But I won't go any deeper into that just now, as this sub-set of blog items is meant only to help identify those missing WinForms favorites and repatriate them as WPF troops.
For some time now, I've been promising myself that one day I will create a list of what I like to think of as "WPF MIA" - those basic elements that I am totally familiar with in WindowsForms, but which somehow seem to have disappeared into thin air in WPF.
In many cases of course the tools are still there; it's just that the name has changed. Here's an example:
In Windows Forms, you can hide the Minimize and Maximize buttons by setting the MinimizeBox or MaximizeBox to False I guess that this is most people's favourite way of doing this. If you dig deeper into the Properties Window though, there is also the FormBorderStyle property.
I used to think that this property was only good for things like changing the border style, say, from 3D to single line. But actually if you choose FixedToolWindow or SizableToolWindow then you automatically get a Form that only has the close button - no Min or Max.
So what about WPF then? Does it have the same properties available?
If you've spent any time on WPF, you'll know that that's really a rhetorical question because you can almost guarantee that things will have changed. Now though you have a WindowStyle property which you can use to set the Window's border style, including showing or hiding the Max and Min buttons. To have those buttons hidden, you need to choose the ToolWindow option.
As far as I know, there isn't a property setting that will hide the individual buttons in in WPF. At first I found this a bit surprising. However, as I've become more familiar with WPF and its ability to morph its Windows into almost any conceivable style and shape, I can see that creating your own Window style is so easy that maybe those properties would have been almost redundant anyway.
Example:
' Create a LINQ query.
Dim buttonQuery = From control In Me.Controls Where TypeOf (control) Is Button
' Create a list of object.
Dim buttonList = buttonQuery.ToList
' Iterate through the objects in buttonList.
For Each btn As Button In buttonList
' Show each button's text property.
MessageBox.Show(btn.Text)
Next
My makes it easy to use .Net. Here's an example that uses My to rename a file:
Syntax:
Public Shared Sub RenameFile ( _
file As String, _
newName As String _
)
file is the file path to the file to be renamed.
newName is the new name (with file extentsion) to be given to the file.
Example:
My
.Computer.FileSystem.RenameFile("C:\OldName.txt", "NewName.txt")
Mike McIntyreGet Dot Net Code
I have just discovered the most compelling reason to port over any projects I have to Windows Presentation Foundataion (WPF) as well as only starting new projects as a WPF project.
The reason you ask? Well the answer is one word.
Printing
Being a File I/O guy, printing has almost always needed to be a part of most projects that I have written so this is a really big thing for me. Looking back printing with Visual Basic.NET was painful for me at times especially if the document spanned several pages and I don't even want to get into printing with pre .NET Visual Basic.
That, however, is the past and today we take a look at the present and future.
After creating a table in a flowdocument I got to thinking about being ability to print out the flowdocument.
The WPF PrintDialog class has two methods for printing. The two methods are PrintVisual and PrintDocument.
This post will deal with the PrintDocument method.
When you use PrintDocument you don’t actually pass the flowdocument that you want to print as a parameter, you use a DocumentPaginator object.
What the documentpaginator does is take the content of the flowdocument and breaks it up into multiple pages (if required) then passes the page(s) to the printer to be printed out.
After importing the necessary namespaces and declaring two Form level variables

I started out with the code to create a flowdocument that goes over more than one page I used the following code.
' Create the parent FlowDocument...
flowDoc = New FlowDocument()
Dim table2 As New Table()
flowDoc.Blocks.Add(table2)
table2.Columns.Add(New TableColumn)
table2.Columns.Add(New TableColumn)
table2.Columns.Add(New TableColumn)
table2.Columns.Add(New TableColumn)
' Create and add an empty TableRowGroup to hold the table's Rows.
table2.RowGroups.Add(New TableRowGroup())
' Add the first (title) row.
table2.RowGroups(0).Rows.Add(New TableRow())
' Alias the current working row for easy reference.
Dim currentRow2 As New TableRow()
currentRow2 = table2.RowGroups(0).Rows(0)
Dim v As New Image
v.Source = New BitmapImage(New Uri("dice.jpg", UriKind.Relative))
v.Height = 80
v.Width = 80
' Add cells with content to the second row.
currentRow2.Cells.Add(New TableCell(New Paragraph(New Run("Loan Report"))))
currentRow2.Cells(0).ColumnSpan = 3
currentRow2.FontSize = 50
currentRow2.Foreground = Brushes.RosyBrown
currentRow2.Cells(0).TextAlignment = TextAlignment.Center
currentRow2.Cells.Add(New TableCell(New BlockUIContainer(v)))
table2.RowGroups(0).Rows.Add(New TableRow())
currentRow2 = table2.RowGroups(0).Rows(1)
currentRow2.Cells.Add(New TableCell(New Paragraph(New Run(" For: Neil Knobbe, " & Now.Date.ToLongDateString))))
currentRow2.Cells(0).TextAlignment = TextAlignment.Center
currentRow2.Cells(0).ColumnSpan = 4
table2.RowGroups(0).Rows.Add(New TableRow())
currentRow2 = table2.RowGroups(0).Rows(2)
currentRow2.Cells.Add(New TableCell(New Paragraph(New Run("Ammount Borrowed: Borrowed Variable" & vbCrLf & _
"Monthly Payment: Payment Variable" & vbCrLf & _
"Interest: Interest Variable" & vbCrLf & _
"Duration: Duration Variable" & vbCrLf & _
"Reason For Loan: Reason Varaible" & vbCrLf & _
"Lender: Lender Variable" & vbCrLf & _
"Loan Number: LoanNum Variable"))))
currentRow2.Cells(0).ColumnSpan = 2
currentRow2.Cells(0).Padding = New Thickness(10)
' Create and add an empty TableRowGroup to hold the table's Rows.
table2.RowGroups.Add(New TableRowGroup())
' Add the first (title) row.
table2.RowGroups(1).Rows.Add(New TableRow())
' Alias the current working row for easy reference.
Dim currentRow As New TableRow()
currentRow = table2.RowGroups(1).Rows(0)
' Add the header row with content,
currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Date"))))
currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Balance"))))
currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Payment"))))
currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Interest"))))
currentRow.Cells(0).Foreground = Brushes.RosyBrown
currentRow.Cells(0).TextAlignment = TextAlignment.Left
currentRow.Cells(1).Foreground = Brushes.RosyBrown
currentRow.Cells(1).TextAlignment = TextAlignment.Left
currentRow.Cells(2).Foreground = Brushes.RosyBrown
currentRow.Cells(2).TextAlignment = TextAlignment.Left
currentRow.Cells(3).Foreground = Brushes.RosyBrown
currentRow.Cells(3).TextAlignment = TextAlignment.Left
table2.RowGroups(1).Rows.Add(New TableRow())
currentRow = table2.RowGroups(1).Rows(1)
currentRow.Cells.Add(New TableCell())
currentRow.Cells(0).ColumnSpan = 4
currentRow.Cells(0).BorderThickness = New Thickness(0, 5, 0, 0)
currentRow.Cells(0).BorderBrush = Brushes.RosyBrown
Dim i As Integer
For i = 2 To 25
table2.RowGroups(1).Rows.Add(New TableRow())
currentRow = table2.RowGroups(1).Rows(i)
currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Column " & i - 1 & " Entry - " & i.ToString))))
currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Column " & i - 1 & " Entry - " & i.ToString))))
currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Column " & i - 1 & " Entry - " & i.ToString))))
currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Column " & i - 1 & " Entry - " & i.ToString))))
Next
ScrollView1.Document = flowDoc
Which, once I applied the table to a flowdocument and set the flowdocument to be the document of my FlowDocumentScrollViewer,

gave me a table with 50 entries, shown in three columns, which I wanted to print.

Now that the flowdocument was ready to print all that was left to do was create instance of a WPF PrintDialog and enter the one line of code needed to print the document.
Yes you heard right, one line of code* to print a WPF flowdocument. How much easier can it get? (And I thought I was happy when the people at MS made reading the entire contents of a file into a single line of code for Visual Studio 2005. I am simple ecstatic about this change to printing!)
(* Note – Although you can accomplish printing with just this one line, there are a few gotchas that you will need to be aware of. I will touch on some of these in a bit.)
So, the magic line of code is:

Ok, I hear all you out there going "Ummm.... Neil that looks like 5 lines of code to me not one like you said." Which would be a valid statement and it is true there is a couple more lines of code.
Basically what I have done is put the print code in an If...End If statement which gives displays the printdialog to the user where they can choose their printer and print the document or cancel the print job all together.
All in all six lines of code to print out a single or multiple page document is nothing.
What is the code doing then?
As I mentioned earlier a new instance of a printdialog is created. The printdialog is the window that is shown where you can select which printer you want to print with and other printing options.
The printdialog is shown as a dialog window and if the user clicks the "OK" button, the flowdocument is cast as a DocumentPaginator object which is used as the first parameter of the printdialog’s PrintDocument. The second parameter of the printdocument is the name of the document being printed. This name is what is displayed in the print queue of the printer so you can uniquely identify what documents are lined up to be printed.
The Else portion of the If...End If statement handles if the user clicks the "Cancel" button. A simple message box is displayed letting the user know that the print job has been cancelled.
That in a nutshell is all that you need to do to print a flowdocument in a WPF application.
Now for the gotchas that caught me while I tried to print.
#1) Multiple columns. Let me tell you I this one really got me. When I first tried to print the flowdocument in the image above, I expected the print out to look like the window. Such was not the case. What I ended up with was the entire table printed out on two columns per page over the three pages* the printed document covered.

This was not quite what I was expecting, but is apparently the default behaviour without having set any properties of the document to match the printer. What I wanted was to have the table fill the entire width of the page.
(* Note - As a rather interesting side note here this is not what I got the first time I tried to print this flowdocument. My first results were rather disappointing and I was very disillusioned with what I thought was going to be a much simplified printing process. The image below is my first attempt at printing.

As you can see it ended up quite the mess. What happened was that all three pages of the flowdocument got printed in two columns on one page. (If you look real close at the left column you can see the header of the table in the background then the first 8 lines have been printed twice.)
I can happily report that the fault with this lies with the printer and not how the WPF printdialog sent the document to the printer. I have since tested several other printers and they all print as the multiple columns image.)
Getting back on track and printing the document on only one column.
The way to get around having multiple columns is to set the column width of the document to be printed prior to casting it as a documentpaginator object. This is a simple process and requires only one line of code.

What this code does is sets the width of document in the scrollviewer to be the entire printable width of the page. Now when the document is printed the table will span the width of the page.
#2) Page size. While perhaps not quite as important as multiple columns is getting the proper page size and margins for your printed output.
You can set the bounds of your document to be printed by setting three properties of the document. The three properties are the PrintableAreaHeight, PrintableAreaWidth and the Thickness.
The PrintableAreaHeight and PrintableAreaWidth properties are pretty self explanatory. The Thickness is what is used for the margin. The thickness can be set with a universal value which will be applied to all sides, or you can set pass different parameters for the margin on each side.

While it is not essential to set these three properties for printing it does ensure that your document fits the page or pages.
Summary
So, with adding in the few lines of code from the two gotchas, I finally was able to print out the report and have it look as I wanted it to.

The entire print code I used was:

There you have it a quick overview of printing in a WPF application. As I said it is much easier and I have my doubts if I will ever look at starting another WinForms application.
vbCity Leader (and Microsoft MVP) Ged Mead showed how to create a FlowDocument in his article published on devCity.com.
I got the challenge recently to display a data inserted in a table in a flowdocument. So I embarked on my quest.
I knew that what I wanted could be done because some quick testing with XAML I was easily able to add a table to my flowdocument.

Which gives an output like the following.

My challenge, however, was that I needed to be able to do this with Visual Basic.NET code behind as the data that was to be displayed in the table was going to be read from a file.
I made my first mistake almost immediately. (Now that is probably not the best thing for me to admit when I am trying to convince everyone that I actually know what I am talking about, but hey, I am big enough to admit my mistakes.)
The mistake I made was trying to use the wrong table. I was able to create and insert the data from the file into the table I created with VB code, but when I tried to add the table to the flowdocument I couldn’t. No matter what I tried, I was just unable to add the table and after a couple of days, I finally gave up and asked for help. Microsoft MVP Jay Harlow quickly pointed me in the right direction.
What the problem was is that there are more than one table that is accessible for use in the .NET Framework. I was trying to use the table in the System.Web Namespace, but in fact I needed to be using the table from the System.Windows.Documents Namespace.
Once I was referencing the correct table, the whole project came together quite nicely.
(While the project I was doing required reading data from a file that will be outside the scope of this blog post. In this post I will just be showing how to hardcode data into a table in a flowdocument.)
The first thing that is needed is to Import the Namespace to give us access to use a table.

Next declare two form level variables. One variable is declared as a flowdocument and the other is declared as a table.

In the event you want to create the table in, I used the Initialized event of the form for this example, set the two variables to a new instance of a flowdocument and table respectively.

Now that there is a flowdocument and a table the table needs colums, rows and content for the table.
The columns are the first things to be added to the table. Columns are added to the collection, or array, of columns of the table. For this example I am going to add four columns to the table.

With the columns added, Rows can now be added to the table. Like columns, rows are also held in an array of rows. Where rows differ from columns is rows are actually part of RowGroups. You can have several rows in a rowgroup and more than one rowgroup in a table.
So to start a new rowgroup must be added to the rowgroups of the table.

and into that rowgroup a new row is added.

Although you still can’t see it if you were to look at the form itself, the table now has four columns and one row. Into the row Cells need to be added.
At this point there is a great deal of possibilities to how what the table is going to look like. While you can set some formatting for the table with changing the properties of the table and rows, you have much greater control over the look of the table by manipulating the properties of the cells.
For this table I want to have a header that stretches across the entire top of the table, so while I can have four cells in the row (I can do this because I added four columns to the table earlier) only one is going to be added to the row. The cell will then be made to stretch over the entire width of the table.

There is quite a bit going on in the above line of code. A new TableCell is added to the collection of cells of the row and to add content to the cell a new Paragraph is used as a parameter of the tablecell and a new Run of text is passed as a parameter of the paragraph.
After adding what is going to be the title of the table, some formatting can be assigned.

The first line of code above tells the cell to span the entire width of the four columns of the table. (If you have worked with tables in Microsoft Word think of this process to be like merging cells in a table.)
The second line sets the size of the font to make the text stand out as a title and the third line centers the text in the cell. (By default the text alignment of a table, row or cell is left.)
The fourth line of code makes the font bold.
The fifth line of code sets the colour of the text in the cell and the sixth line gives the cell a colour for the background.
If we add two more lines of code the table can be shown in the FlowDocumentReader.
The table is first added to the Blocks of the flowdocument that was declared at the beginning of our code then the flowdocument is assigned as the document of the FlowDocumentReader of the form.

(Note: Interestingly enough it does not seem to matter where you place the code to add the table into the flowdocument and make the document of the reader to be your flowdocument. The code can be added just after the declaration of the flowdocument and table or anywhere in the middle of the code that formats and populates the table. Personally I put these two lines at the very end prior to displaying the flowdocument.)
Now when the project is run, you can see the table starting to take shape.

Putting Headers for each column are next to be added to the table.
A new row is added to the rowgroup of the table then four cells are added to the row with content also being added to each cell.

You can see by the Index number of the rows that the new cells have been added to the second row. Like all .NET arrays the rows array of a rowgroup, as is the rowgroup itself, are zero based so if you need to remember that the actual row number is one higher than the index number.
Running the project shows that the columns now have headers.

The table is really starting to look like a table now.
A line under the column headers keeps the data in the columns separated from the headers. To accomplish this, a new row is added under the row of headers. One cell is added to the row and it will span the four columns like the title cell does. To make the line, the border property of the cell is going to be used. The top border of the cell will be made larger and given a colour.
To make the border thicker a New Thickness is applied to the cell. Thickness can either be set by passing a single parameter to the new thickness, which would give the same thickness to all sides of the cell, or you can pass four parameters and assign separate thicknesses to each side. While it does not make any difference to what I am demonstrating here, I have only assigned a thickness to the top border of the cell. I could have just as easily assigned a uniform thickness to the cell.

Now the contents of the table will be separated from the column headers by a red line.

With the formatting of the table set out, it is time to add some content into the table itself.
For this example I am going to use a For…Next Loop to populate the table.

Two Integer variables are declared. The first is used to keep track of the index of the row in the rowgroup. The variable is declared with a value of 3 because the first line that will be added in the loop will be the fourth element of the array of rows. The second variable is just used for adding content to each of the cells as they are added to the row.
In the loop the first line of code adds a new row to the table then the next four lines add the cells to the row and put some content in the cell. In this example the content of the cells in the row will just have the text "Number: 0" to "Number: 97".
In the last line the value of the variable j is incremented by 1 so the content number of the next row will be one higher than the last.
Now when the project is run the table is complete. The table starts with:

and six pages later going to:
