Archive for the ‘WPF’ Category

XamComboBox ComboBoxItemsProvider Very Slow In XamDataGrid

October 25, 2011

I had a page which took 10 seconds to load. When I removed the XamDataGrid Column containing my ComboBoxProvider it took less than 2 seconds to load.

The fix to make it faster was to initialize the ComboBoxProvider through a much faster class. Because ComboBoxProvider is being created once per row, the savings were massive. I also threw away some redundant Binding which cut down on processing time for every DataGridRow.

So I changed this:

igEdit:ComboBoxItemsProvider x:Key="SlowOperationTypeProvider"
ItemsSource="{Binding Source={x:Static enum:EnumDictionary.OperationTypeDictionary}}"
DisplayMemberPath="Key" ValuePath="Value"

To This

igEdit:ComboBoxItemsProvider x:Key="FastOperationTypeProvider"
ItemsSource="{Binding Source={x:Static classlib:Defaults.OperationTypes}}"

EnumDictionary uses Reflection to read a .NET DescriptionAttribute which contains a user-friendly Description for my OperationType Enum. SlowComboBoxItemsProvider iterated through the Enum, used Reflection and Object Casting to create its Dictionary, then used Reflection again to perform DataBinding.

FastComboBoxItemsProvider extracted only the Keys (strings) from an already created EnumDictionary and did not require DisplaymMemberPath or ValueMemberPath attributes. Like I say, I saved 8 seconds in a DataGrid containing only 10 rows.

Advertisements

System.Configuration.ConfigurationErrorsException The Process Was Terminated Due To An Unhandled Exception.

September 26, 2011

We were getting these error in our UAT environment, but not DEV. Betcha you are too.

The reason for this error is that your Config file does not have the structure expected by the .NET runtime. This means that the app. crashes when the Configuration File is accessed.

The particular way in which our config file was malformed was that we were missing the Oracle.DataAccess.Client Section within system.data which was expected because the runtime was trying to load that section due to the presence of a DbProviderConfigurationHandler.

That DbProviderConfigurationHandler was being invoked because we are using Entity Framework connecting to an Oracle Database. EntityFramework was attempting to load various Oracle bits and pieces because the connection string in the app.config within the Visual Studio Class Library Project that connected to the database (i.e. our DAO project) was attempting to load the Oracle DataAccess Provider. This invoked the DbProviderConfigurationHandler but the relevant section in the app.config was not present.

Later on when we manually added the Oracle.DataAccess.Client section we got a crash because the tester’s machine did not have a full installation of Oracle Client for Entity Framework. That’s this sucker.

Now, solving this this was an enormous relief to us because that MinDate setting was being used to dynamically populate an Infragistics ValueConstraint control. When app.config was accessed that control would throw a humungous StaticMarkup Exception. Now this made sense because the ValueConstraint was being populated through a WPF Style but fooled us into thinking that somehow our tester’s machine (running XP) were incompatible with the Infragistics ValueConstraint control. Our DEV machines (running Windows 7) were all fine with the ValueConstraint control.

When we took the ValueConstraint control out (by commenting out the Style) the tester’s machine loaded a cut-down version of our app. fine, but that was only because the cut-down app no longer accessed app.config. Nothing to do with the actual ValueConstraint control at all.

After we removed the ValueConstraints we no longer got StaticMarkup Exception but we did get ConfigurationErrorException and that led us on a Phase 2 Journey Of Pain that, via Divide and Conquer, finally showed us that loading EntityFramework was the REAL real issue.

But not now.

Gloat.

So, bottom line: check that all the Sections expected by your Section Handlers are present. Go back to machine config and see what’s being loaded there and make sure everthing you are expecting to be installed is really installed.

Locating DataContext Of Infragistics WPF ContentPane from the ContentPaneTabHeader

September 19, 2011

Dammit, I forgot the juicy parts of how I went about resolving this particular Jouney Of Pain, but the answer is: to locate the DataContext of the ContentPane from the ContentPane TabHeader you must navigate to the PaneTabItem and descend through two DataContexts.

Here’s one of me doing the above, Binding the Fill Property of a Rectangle to a Property on the ContentPane. (Yes, this does assist World Peace).

Fill="{Binding Path=DataContext.DataContext.whatever,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type igDock:PaneTabItem}}}"

System.Windows.Markup.XamlParseException when starting WPF Application

September 13, 2011

In all likelihood you have forgotten to bundle a third-party dll with your deployment.

In my case I had neglected to set “Copy To Local” to true for the dlls from the Infragistics control set. Because they were installed on the GAC in my DEV machine the deployment bundle ran fine on DEV but they were not in the GAC on TEST hence the crash.

So make sure all your third party dlls are deployed.

For Lovers Of Stack Traces, here’s the whole horror show, excised from the Windows Event Log. Enjoy(ish)

Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.Windows.Markup.XamlParseException
Stack:
at System.Windows.Markup.XamlReader.RewrapException(System.Exception, System.Xaml.IXamlLineInfo, System.Uri)
at System.Windows.Markup.WpfXamlLoader.Load(System.Xaml.XamlReader, System.Xaml.IXamlObjectWriterFactory, Boolean, System.Object, System.Xaml.XamlObjectWriterSettings, System.Uri)
at System.Windows.Markup.WpfXamlLoader.LoadBaml(System.Xaml.XamlReader, Boolean, System.Object, System.Xaml.Permissions.XamlAccessLevel, System.Uri)
at System.Windows.Markup.XamlReader.LoadBaml(System.IO.Stream, System.Windows.Markup.ParserContext, System.Object, Boolean)
at System.Windows.Application.LoadBamlStreamWithSyncInfo(System.IO.Stream, System.Windows.Markup.ParserContext)
at System.Windows.Application.LoadComponent(System.Uri, Boolean)
at System.Windows.Application.DoStartup()
at System.Windows.Application.b__1(System.Object)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(System.Object)
at System.Threading.ExecutionContext.runTryCode(System.Object)
at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode, CleanupCode, System.Object)
at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
at MS.Win32.HwndWrapper.WndProc(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(System.Object)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)
at System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Int32)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(System.Windows.Interop.MSG ByRef)
at System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)
at System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame)
at System.Windows.Threading.Dispatcher.Run()
at System.Windows.Application.RunDispatcher(System.Object)
at System.Windows.Application.RunInternal(System.Windows.Window)
at System.Windows.Application.Run(System.Windows.Window)
at System.Windows.Application.Run()
at SubstationLoad.App.Main()

Watermark in Filter Row of XamDataGrid

September 7, 2011

This one took some avid Googling and a lucky guess.

To put a Watermark in the Filter Row of a XamDataGrid from Infragistics you need to create a new FilterCellEditorStyle for the XamComboBox which is the editor control in that filter row.

All kudos to Alex Fidanov of Infragistics support who, in this Infragistics Community Thread, “How to show empty string as DisplayText instead of value when value is not found in combo’s ItemsSource”, gave the basic answer.

The basic idea is to swap the Watermark in and out as a complete piece of Content for the XamComboBox, that content replacing the XamCombo’s usual Content.

Here’s WatermarkComboEditorStyle:

Style x:Key="WatermarkComboEditorStyle" TargetType="{x:Type igEdit:XamComboEditor}"
Setter Property="Template"
Setter.Value
ControlTemplate TargetType="{x:Type igEdit:XamComboEditor}"
Border x:Name="MainBorder" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Grid
Grid.ColumnDefinitions
ColumnDefinition Width="*"/
ColumnDefinition Width="Auto"/
/Grid.ColumnDefinitions
Grid.RowDefinitions
RowDefinition/
/Grid.RowDefinitions
Border Grid.Column="0" Margin="1,0,0,0" Padding="{TemplateBinding Padding}"
Grid
ContentPresenter x:Name="PART_Content"
Content="{Binding DisplayValue, ConverterParameter='', Mode=OneWay,
RelativeSource={RelativeSource TemplatedParent}}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/
!-- This text box is the actual watermark --
TextBlock x:Name="PART_NullText"
Text="Watermark Text" Visibility="Collapsed" FontStyle="Italic"
Foreground="Gray" FontWeight="Bold" /
/Grid
/Border
ToggleButton x:Name="PART_DropDownButton" ClickMode="Press" Grid.Column="1" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}" Style="{TemplateBinding DropDownButtonStyle}" Visibility="{TemplateBinding DropDownButtonVisibility}" /
/Grid
/Border
ControlTemplate.Triggers
Trigger Property="IsEnabled" Value="True"
Setter Property="Visibility" TargetName="PART_DropDownButton" Value="Visible"/
Setter Property="Visibility" Value="Visible" TargetName="PART_NullText"/
Setter Property="Content" Value="" TargetName="PART_Content"/
/Trigger

/ControlTemplate.Triggers
/ControlTemplate
/Setter.Value
/Setter
/Style

Once you have created the Style, assign it to FilterCellEditorStyle in the FieldSettings for the Datagrid like so:

Style x:Key="XamDataGridFiltered" TargetType="{x:Type igdp:XamDataGrid}"
BasedOn="{StaticResource XamReadOnlyDataGrid}"
Setter Property="FieldSettings"
Setter.Value
igdp:FieldSettings AllowRecordFiltering="True" FilterCellEditorStyle="{StaticResource WatermarkComboEditor}"
/igdp:FieldSettings
/Setter.Value
/Setter
/Style

Alex is now awarded the title of Slavic Programming Legend. Congrats Alex.

Locating a Specific ResourceDictionary Within A MergedDictionary

August 5, 2011

Chances are you don’t really need to do this. In all probability you are latranating up the wrong woody perennial.

Answer this question: why do you want to locate a specific ResourceDictionary in the first place? Probably, like yours truly, you want to locate a specific resource within that particular ResourceDictionary. Like me you may have written some abomination like this:


return (DataTemplate)Application.Current.Resources.
MergedDictionaries[0][key]

Yes, enough to make you reach for the nearest brown paper bag isn’t it.

Good News: You don’t need to know the specific ResourceDictionary to locate your resource. And that’s because Dictionaries, including Merged Dictionaries are gigantic Hash Tables which means that all your resources are uniquely keyed within the Merged Dictionary itself. Which means you don’t need to know which are ResourceDictionary contains your resource. Which means you can simply write:

return (DataTemplate)Application.Current.Resources[key]

There. That feels better doesn’t it ?

Binding ItemsSource of TabControl in MVVM: No Tabs Visible

July 13, 2011

Once again, just to save you the same embarrassment, I will document a severe moment half-day of silliness that caused me a classic Journey Of Pain.

The short story was that I was binding my TabControl ItemsSource to an ObservableCollection but that no TabItems were being generated.

And the short story of the solution was that the ViewModel property containing the ObservableCollection was not public hence it was not accessible to the TabItem control.

Yep that’s right and that’s all. Since the ObservableCollection lives in another class (i.e. the ViewModel) to the View which access it, the access modifer needs to be suitably public. Yes, I know I am a churlish, dismal-dreaming fustilarian but before you gnaw your thumb at me, Sirrah just call to mind your latest coding embarrassment until you feel more chaitable. Finished? …. Good.

So, just to repeat that, if you are not seeing your ObservableCollection in your TabControl ItemsSource, make sure that the ViewModel property it is binding to is marked PUBLIC..

More Good News

You may not realise this, but Visual Studio by default will report WPF run-time Binding errors in your Debug Output Window. Mine looked like this:


System.Windows.Data Error: 40 : BindingExpression path error: 'TabReports' property not found on 'object' ''MainViewModel' (HashCode=32685253)'. BindingExpression:Path=TabReports; DataItem='MainViewModel' (HashCode=32685253); target element is 'TabControl' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')

For more ways to debug WPF Binding errors go to this very useful page “Debugging Data Bindings in a WPF or Silverlight Application” written by your friend and mine at MSDN blogs karl140.6 (I notice he has recently upgraded himself). Brave man, karl and thank you.

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.

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”

WPF: Replicating MDI Application Behaviour

November 25, 2009

I decided to use Prism, aka Microsoft Composite Application Guidance For WPF and Silverlight.

This is very much like using a supersonic jet fighter to pop down to the local shops but its effective, not very hard and does not require you to write reams of fragile custom code. You just need to spend a bit of time getting used to Prism. Now that’s not a trivial task, but you should have covered the fundamental concepts within a working week, assuming you have some prior knowledge of Dependency Injection.

The huge advantage to you is that Prism is written by people who are much better programmers than you or I are, so the code is robust, whereas writing a custom MDI Window Manager is out of the league of most Intermediate-level developers (most of us).

Very simply, Prism allows the development of Modular Applications in which the User Interface of an application is decomposed into loosely-coupled Modules, each of which occupies a specific area on the screen, known as a Region.

No window (known in Prism as a View) can move outside the Region in which it is contained. This replicates the MDI-like behaviour you’re looking for.

Prism is also a library of Best Practices – it’s produced by the Microsoft Patterns and Practices team – so you get heaps of built-ins for free. And there’s a helpful forum where others using Prism are happy to help us newbies.

You can also have multiple windows (views) open simultaneously if you want, but this will render as a tabbed interface which is not exactly the same as MDI.

I embarked upon this approach and have been successful in producing a MDI-like app. It wasn’t too hard and I learnt heaps along the way. Go for it.

Update 27-Jan-2010

Ahmed in comments asked me to post the code for my MDI sample.

All I did was use a WPF ContentControl for each Prism Region. A ContentControl can only display one View at any one time, so each time you activate a View the one currently in the Region becomes invisible and the new one you activated becomes visible.

This provides the illusion that there is only one window present at any one time. In fact, all the Views you activated are still there, just invisible until you explictly RegionManager.Remove the View from the Region. This is kind of good though, because the ‘invisible’ views are there in memory and so become visible again instantly you do RegionManager.Activate on them. Effectively this is caching the Views for performance.

So here’s my Shell Window markup:

Grid/
ContentControl Grid.Column="1" Grid.Row="1" cal:RegionManager.RegionName="MainRegion"
VerticalContentAlignment="Top"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"

ContentControl Grid.Column="1" Grid.Row="1" Name="AdminRegion"
cal:RegionManager.RegionName="AdminRegion"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Grid.ColumnSpan="2"

ContentControl Grid.Column="2" Grid.Row="1" cal:RegionManager.RegionName="ToDoListRegion"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"

/Grid

So you notice I have three ContentControls, one for each of my regions. I also have a Menu with a gazillion MenuItems (not shown).

Each Menu Item is bound to a Prism DelegateCommand like so:

'''
''' Initialise Commands On The Module Menu
'''
Private Sub InitialiseContentCommands()
Me.ContentShowLocalContentCommand = _
New DelegateCommand(Of Object)(AddressOf Me.ShowLocalContent)
Me.ContentShowModuleContentCommand = _
New DelegateCommand(Of Object)(AddressOf Me.ShowModuleContent)
Me.ContentShowModuleDetailsCommand = _
New DelegateCommand(Of Object)(AddressOf Me.ShowModuleDetails)
End Sub

And here’s the ViewModel code related to those DelegateCommands to show the View:

#Region "Module View Injection..."

'''
''' Show Module Content View
''' Update MainRegion with Module Content View
'''
Public Sub ShowModuleContent()
Dim mainRegion As Region = mainView._regionManager.Regions(RegionName.MainRegion)

Dim existingView As Object = mainRegion.GetView(ViewName.ModuleContentView)

If (existingView Is Nothing) Then

Dim moduleContentViewModel As IModuleContentViewModel = _
mainView._container.Resolve(Of IModuleContentViewModel)()
mainRegion.Add(moduleContentViewModel.View, ViewName.ModuleContentView)
mainRegion.Activate(moduleContentViewModel.View)
Else
mainRegion.Activate(existingView)
End If

End Sub

#End Region

Also in the Shell ViewModel I handle a Prism CompositeEvent that is published every time a View is closed in the MainRegion (i.e. by the User clicking on the Close Button for a View. This removes (destroys) the View as opposed to just letting it get overwritten by the next View that gers activated.

#Region "Event Handlers..."

'''
''' Handler for Composite CloseViewEvent
'''
Public Sub CloseViewEventHandler(ByVal closeViewInfo As CloseViewInfo)
Dim regionManager As RegionManager = _
mainView._container.Resolve(Of IRegionManager)()

'Find the region
Dim regionQuery = _
From regn In regionManager.Regions _
Where regn.Name = closeViewInfo.RegionName _
Select regn

' Find the View in the region
Dim region As Region = regionQuery.Single()
Dim view As Object = region.GetView(closeViewInfo.ViewName)

' Remove the view
If view IsNot Nothing Then
region.Remove(view)
End If

' Since ContentControl can only show one view at a time, if any one View is closed
' then no views are visible
Me.AllViewsClosed()
End Sub

I also have a CloseAll MenuItem which closes all the cached (invisible) Views in the Main Region, to give the users and the application the opportunity to clean up all those Views. This is done by having every View Register its CloseButton Command with the a Prism CompositeCommand called CloseAllCommand and having every View provides a handler which Publishes a Prism Event Aggregation event I defined called CloseView.

The Shell ViewModel subscribes to the Event Aggregation CloseView event and removes the calling View from the MainRegion. Since all Views do this, all Views are Removed

In XAML:

MenuItem Header=”_ Close Background Windows” Name=”mniCloseAll”
Command=”{x:Static inf:GlobalCommand.CloseAllCommand}” FontSize=”9″ /

In the ViewModel:

”’
”’ Subscribe to Event Aggregator CloseViewEvent
”’
”’
Private Sub SubscribeCloseViewEvent()

Dim ea As EventAggregator = mainView._container.Resolve(Of IEventAggregator)()

‘ Subscribe to CloseViewEvent
Dim closeViewevent As CloseViewEvent = ea.GetEvent(Of CloseViewEvent)()

If _closeViewSubscriptionToken IsNot Nothing Then
closeViewevent.Unsubscribe(_closeViewSubscriptionToken)
End If

‘ Perform the event on the UIThread
‘ keepSubscriberReference Alive is false. This means the event maintains a
‘ weak reference to the subscriber instance, allowing the garbage collector to
‘ dispose the subscriber instance when there are no other references to it.
‘ When the subscriber instance gets collected, the event automatically unsubscribes.
_closeViewSubscriptionToken = _
closeViewevent.Subscribe(AddressOf CloseViewEventHandler, ThreadOption.UIThread, False)
End Sub

So hopefully you can see that each region only has one View visible at any one time (kind of like a MDI application that I am porting) and because the Views are situated in a Region, they can’t move out of there, which is MDI-like behaviour. No letting your users drag windows all over the screen and losing them.

Careless users.