Archive for January, 2010

Integrating WPF Ribbon With Prism and MVVM (with VB.NET example)

January 31, 2010

The WPF Ribbon control is an exciting UI element that provides instant Wow factor to applications and is fun to use, but the current CTP version for Framework v3.5 which is a parallel release for the WPF June 2009 toolkit, does not natively integrate with Prism or MVVM.

Firstly, the RibbonCommand is based on WPF’s standard RoutedUICommand class which does not have the same capacities as Prism’s DelegateCommand and secondly the RibbonCommand requires its CanExecute and Execute Event Handlers in Code Behind which violates the MVVM pattern. In addition, RibbonCommands must be declared as Static Resources which is an unusual restriction on WPF development.

Fortunately there are some smart programmers out there who have grappled with these issues. It turns out you need to:

  1. Sub-class RibbonCommand in order to delegate (that’s dele-GATE as a verb) the RibbonCommand to a Prism DelegateCommand via ICommand;
  2. Provide a Bridging Class which allows Static Resources to connect to a ViewModel Property instead of requiring EventHandlers in Code Behind.

The above is the discovery of sergioesp on the Prism Discussion Forum. I translated his code into VB.NET and added support for InputGestures (Menu Short Cuts).

SergioEsp, you are a legend!

So here’s the beef…

In the XAML for the RibbonWindow you need the following markup:

NB, inf is the xmlns for my Infrastructure project in which I have defined the classes CommandReference and DelegatedRibbonCommand

Window.Resources
inf:CommandReference x:Key="SaveAllCommandReference"
Command="{Binding SaveAllCommand}"/

inf:DelegatedRibbonCommand
x:Key="SaveAllCommand"
LabelTitle="Save All"
LabelDescription="Save all edits (Ctrl+S)"
ToolTipDescription="Save All edits"
ToolTipTitle="Save All (Ctrl+S)"
SmallImageSource="/Images/Ribbon/disk.png"
LargeImageSource="/Images/Ribbon/disk.png"
InputGestureKey="Ctrl+S"
DelegatedCommand="{StaticResource SaveAllCommandReference}" /

/Window.Resources

CommandReference is the Bridging Class that allows StaticResources to connect to a ViewModel Property by exposing a Dependency Property. SergioEsp got this class by using the WPF Futures MVVM Visual Studio Template, version 0.1. I didn’t want to use the MVVM Template, so I just downloaded the code and stole the CommandReference class. Here it is, translated to VB.NET

Imports System
Imports System.Windows
Imports System.Windows.Input

'''
''' This class facilitates associating a key binding in XAML markup to a command
''' defined in a View Model by exposing a Command dependency property.
''' The class derives from Freezable to work around a limitation in WPF when data-binding from XAML.
'''
'''
''' Taken from MSDN WPF Futures MVVM Toolkit v0.1 source. Converted from C# to VB
'''
Public Class CommandReference
Inherits Freezable
Implements ICommand

#Region "Constructor..."

Public Sub CommandReference()
'// Blank
End Sub

#End Region

#Region "Properties..."

Public Shared ReadOnly CommandProperty As DependencyProperty = _
DependencyProperty.Register("Command", GetType(ICommand), GetType(CommandReference), _
New PropertyMetadata(New PropertyChangedCallback(AddressOf OnCommandChanged)))

Public Property Command() As ICommand
Get
Return CType(GetValue(CommandProperty), ICommand)
End Get
Set(ByVal value As ICommand)
SetValue(CommandProperty, value)
End Set
End Property

#End Region

#Region "ICommand Members"

Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute

If Not Command Is Nothing Then
Return Command.CanExecute(parameter)
End If

Return True
End Function

Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
Command.Execute(parameter)
End Sub

#End Region

Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged

Private Shared Sub OnCommandChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)

Dim commandReference As CommandReference = CType(d, CommandReference)
Dim oldCommand As ICommand = TryCast(e.OldValue, ICommand)
Dim newCommand As ICommand = TryCast(e.NewValue, ICommand)

'If oldCommand IsNot Nothing Then
' 'RemoveHandler oldCommand.CanExecuteChanged, commandReference.CanExecuteChanged
'End If
'If newCommand IsNot Nothing Then
' newCommand.CanExecuteChanged += commandReference.CanExecuteChanged.Canxecute

'End If

commandReference.Command = newCommand
CommandManager.InvalidateRequerySuggested()

End Sub

#Region "Freezable"

Protected Overrides Function CreateInstanceCore() As Freezable

Throw New NotImplementedException()
End Function

#End Region

End Class

The DelegatedRibbonCommandClass is a translatation of SergioEsp’s RibbonCommandEx class with added support for InputGestures. As you can see, the RibbonCommand is made to use the CanExecute and Execute handlers of the ICommand in the DelegatedCommand Property of DelegatedRibbonCommand.

Since we are using Prism, that DelegatedCommand Property is a Prism DelegateCommand. The ViewModel code is shown further below, but first, here’s the DelegatedRibbonCommandClass:

Imports System.Windows.Input
Imports Microsoft.Windows.Controls.Ribbon

'''
''' Class to delegate a RibbonCommand to an ICommand.
''' Allows RibbonCommands to be used within WPF Prism in a MVVM design pattern
'''
'''
''' Code taken from MSDN Prism Discussion Forum
''' http://wpf.codeplex.com/Thread/View.aspx?ThreadId=52089
'''
Public Class DelegatedRibbonCommand
Inherits RibbonCommand

'''
''' KeyGestureConverter for all DelegatedRibbonCommand instances
'''
'''
Private Shared _myKeyGestureConverter As KeyGestureConverter
Public Shared ReadOnly Property MyKeyGestureConverter() As KeyGestureConverter
Get
If _myKeyGestureConverter Is Nothing Then
_myKeyGestureConverter = New KeyGestureConverter
End If
Return _myKeyGestureConverter
End Get
End Property

'''
''' ICommand that this RibbonCommand will delegate to
'''
'''
Private _delegatedCommand As ICommand
Public Property DelegatedCommand() As ICommand
Get
Return _delegatedCommand
End Get
Set(ByVal value As ICommand)

Me.SetKeyGesture()

If Not _delegatedCommand Is value Then
_delegatedCommand = value
If Not _delegatedCommand Is Nothing Then
AddHandler Me.CanExecute, AddressOf DelegatedRibbonCommand_CanExecute
AddHandler Me.Executed, AddressOf DelegatedRibbonCommand_Executed
Else
RemoveHandler Me.CanExecute, AddressOf DelegatedRibbonCommand_CanExecute
RemoveHandler Me.Executed, AddressOf DelegatedRibbonCommand_Executed
End If
End If
End Set
End Property

'''
''' Property which contains String for InputGesture
'''
'''
Private _inputGestureKey As String
Public Property InputGestureKey() As String
Get
Return _inputGestureKey
End Get
Set(ByVal value As String)
_inputGestureKey = value
End Set
End Property

#Region "Event Handlers..."
'''
''' Event handler for Execute event.
''' Calls Execute method on DelegateCommand
'''
'''
'''
'''
Private Sub DelegatedRibbonCommand_Executed(ByVal sender As Object, _
ByVal e As ExecutedRoutedEventArgs)
DelegatedCommand.Execute(e.Parameter)
End Sub

'''
''' EventHandler for CanExecute event
''' Calls CanExecute function on DelegatedCommand
'''
'''
'''
'''
Private Sub DelegatedRibbonCommand_CanExecute(ByVal sender As Object, _
ByVal e As CanExecuteRoutedEventArgs)
e.CanExecute = DelegatedCommand.CanExecute(e.Parameter)
End Sub

#End Region

'''
''' Set the KeyGesture for this DelegatedRibbonCommand
'''
'''
Private Sub SetKeyGesture()
If Not String.IsNullOrEmpty(Me.InputGestureKey) Then
MyBase.InputGestures.Add( _
MyKeyGestureConverter.ConvertFromString(Me.InputGestureKey))
End If
End Sub

End Class

In the ViewModel, we define the Prism DelegateCommand that we want the Ribbon to use. This is standard Prism and MVVM code.

Private _saveAllCommand as ICommand
Public Property SaveAllCommand() As ICommand
Get
return _saveAllCommand
EndGet
Set(value As ICommand)
_saveAllCommand = value
EndSet

In ViewModel Constructor or some arbitrary InitialiseCommands method:
SaveAllCommand = new DelegateCommand(Of Object)(Address Of Me.SaveAll)

The StaticResource to the ViewModel Property by the markup further up.
The WPFRibbon then uses the Prism DelegateCommand in the ViewModel by referencing the StaticResource.
Note below that the SaveAllCommand is the x:Key for the DelegatedRibbonCommand in the Window Resource, NOT the ICommand ViewModel Property.

r:Ribbon.ApplicationMenu
r:RibbonApplicationMenu Command="{StaticResource MainMenuRootCommand}"
!-- Hack to make sure sub-menu items appear.
Add a RecentItems List
http://www.chakkaradeep.com/post/Proper-way-to-create-Menus-2b-Submenus-in-WPF-Ribbon.aspx--
r:RibbonApplicationMenu.RecentItemList
Rectangle Height="200"/Rectangle
/r:RibbonApplicationMenu.RecentItemList
r:RibbonApplicationMenuItem Command="{StaticResource SaveAllCommand}" /
r:RibbonApplicationMenuItem Command="{StaticResource CloseAllCommand}" /
/r:RibbonApplicationMenu
/r:Ribbon.ApplicationMenu

Happy to answer any questions if any of the above is not clear.

The word in the forums is that Microsoft are rewriting the WPF Ribbon to make it natively compatible with MVVM so hopefully before long we won’t need these additional classes to make it possible.

Advertisements

Conditionally Execute HTML In Internet Explorer and/or Non-Internet Explorer Browsers (and How to Provide an Alt Image If Flash Is Disabled)

January 29, 2010

Our Web Monkeys Were Collapsing With Exhaustion…
…so the developers were summoned from their ivory tower to lend a hand. My job, to write the temporary US Landing Page for our company while Marketing smashed their heads together to devise an irresistible glob of eye candy for the permanent site.

I had a requirement to show a Flash file on the Web Page. The Flash file had to be shown in IE6, IE8 and Firefox. I was given an example Web Page and was told that the lead tester’s machine commonly hung while viewing our pages in IE6.

Embedding Flash

The HTML for embedding a Flash object is quite different in Firefox than in IE.

The HTML for embedding a Flash object in IE starts out like this:
object classid=”clsid:D27CDB6E-AE6D-11cf-96B8-444553540000″

whereas in Firefox it looks like this:
object type=”application/x-shockwave-flash”

If IE6 encounters the object tag for the Firefox definition of a Flash file it hangs, whereas IE8 eats it and ignores it.

“Cool”, I thought, “now I know why our lead tester’s machine hangs all the time. I must be a freaking genius to figure out this problem in about 30 seconds flat.”

It seemed strange to me that our Web Monkeys hadn’t figured this out before.

The sample web page I was pointed to had this code in it:

cross browser html

That first block at the top, the one starting with with [If IE], rendered totally in green text inside Visual Studio means ‘Comment’ so the first thing I did was throw away the comments to get a clean file to work with.

Did you spot my Journey Of Pain beginning ?

Yes, that Green text was a comment all right, but my ignorant non-Web Monkey consciousness did not know that they were very special comments indeed, known as Conditional Comments, specifically designed for Cross-Browser implementations just like the one I had been assigned.

In other words, I had just thrown away the core functionality I needed to get the task completed. THE PAIN! THE PAIN! THE ROTTING PAIN!

So it took me about two days to finally figure out that I needed those ‘comments’ and that the template which had been given to me was totally correct. I just needed to update file names and links but instead spent three-fifths of an eternity trawling the Web for exotic and completely unneeded browser detection strategies.

The bitter irony is that if I had not been told that our lead tester’s machine uang on IE6 then I would not have thought that the code I had been supplied was buggy or redundant. AARGH! For those with Accessibility Options turned on I will type that again AAAAAARGGGGHHHH!!.

Yeah, so that code at the top is perfectly fine. It shows a Flash file in IE6, IE8 and Firefox. And the anchor tag underneath the two Flash object blocks shows an Alternate Image in case Flash is disabled in either IE or Firefox.

…and if you hadn’t guessed yet the reason our lead tester’s machine hangs in IE6 has absolutely nothing to do with cross-browser Flash issues. That was merely a monstrous bioluminescent red herring (also here)

Here’s How It Works

Conditional Comments ONLY work in IE for Windows and a few other particular browsers. As David Hammond explains in CSS Hacks – The Good, The Bad And The Ugly

Conditional Comments apply specifically to browsers using Internet Explorer’s Trident layout engine, meaning IE-based browsers like Maxthon and Avant handle them like Internet Explorer does while browsers using other layout engines see them simply as regular comments. Internet Explorer on the Mac uses a different layout engine and doesn’t support conditional comments.

So when IE lumbers along and reads that first Comment [If IE], it goes “Yee-har. That’s for me. Let me interperet that sucker.” and when it finds <!– [if !IE] it does the reverse and ignores it as serenely as lunchtime crowds do the homeless.

On the other hand, non-IE browsers see the first block as a standard comment and therefore ignore it; but later ignore ONLY the Conditional Comment tags [if !IE] that frame the Firefox Flash definition as they are, in fact, also standard everyday comments if you look at the structure. So non-IE browsers read and interperet only the HTML inside the [If !IE] Conditional Comments whereas IE ignores the lot.

Clever eh?

Wish I had read Wikipedia’s page on Conditional Comments before I started, but then I didn’t even know there was such a beast before I started. Cost me two days of precious heartbeats that. THE ROTTING PAIN!!

WPF: Validating ComboBox

January 4, 2010

Happy New Decade!!

Through inefficient Googling this one cost me more time that it needed to, but the answer on how to validate a ComboBox in WPF turns out to be very simple. Just Bind to SelectedItem and have a WPF ValidationRule for the type of the class in the ComboBox list.

In XAML:

ComboBox.SelectedItem
Binding Path=”SelectedCustomer”
Mode=”TwoWay”
BindingGroupName=”FormBindingGroup”
Binding.ValidationRules
local:CustomerValidation /
ExceptionValidationRule /
/Binding.ValidationRules
/Binding
/ComboBox.SelectedItem
/ComboBox

In Code-Behind or ViewModel:

Private _SelectedCustomer As Customer
Public Property SelectedCustomer() As Customer _
Get
Return _SelectedCustomer
End Get
Set(ByVal value As Customer)
_Customer = value
End Set
End Property

CustomerValidation class:
”’
”’ Class to encapsulate Customer validation
”’
”’
Public Class CustomerValidation
Inherits ValidationRule

Public Overrides Function Validate( _
ByVal value As Object, ByVal cultureInfo As CultureInfo) As System.Windows.Controls.ValidationResult

If Not TypeOf (value) Is WebLibrary.Customer Then
Return New ValidationResult(False, “Customer not selected”)
ElseIf value Is Nothing Then
Return New ValidationResult(False, “Customer not selected”)
Else
Return ValidationResult.ValidResult
End If

End Function

And in a Resource File you have the Style Triggers which show the validation messages in a ToolTip as embraced by the Greater Google:

!– TextBoxes
Failing Validation get a ToolTip with the error message in it —
Style x:Key=”ValidatedControl” TargetType=”{x:Type Control}”
Style.Triggers
Trigger Property=”Validation.HasError” Value=”true”
Setter Property=”ToolTip”
Value=”{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}” /
/Trigger
/Style.Triggers
/Style

Style x:Key=”ValidatedComboBox” TargetType=”{x:Type ComboBox}” BasedOn=”{StaticResource ValidatedControl}” /
Style x:Key=”ValidatedTextBox” TargetType=”{x:Type TextBox}” BasedOn=”{StaticResource ValidatedControl}” /

Naturally somehere you will need to make a call to update the BindingSource in order to make the Validators fire
e.g.

‘Forces call to Validators. Just calling BindingGroup.UpdateSources doesn’t achieve that.
For Each bindingExpression As BindingExpressionBase In Me.MainForm.BindingGroup.BindingExpressions
bindingExpression.UpdateSource()
Next

Acknowledgements

Wei Zhou, moderator of the .NET Development Forum WPF group will definitely receive a glorious gift pack of premium jasmine-scented Tadpole Roe,
for solving my immediate problem in the very uncryptically named thread “Combo Box Validation” (how did I miss it for two hours); and

Beth Massi, of Visual Basic Developer Centre receives a complimentary quarter-tonne package of Tadpole-flavoured Cashew Nuts courtesy of our proud sponsors Reject Fish Products Inc., for her very nice Resource File layout for the Style Triggers in “Displaying Data Validation Messages In WPF”