Tuesday 25 September 2012

Command Line Parameter trailing backlash problem

I am currently working on a small console application which takes in a path name via the Command Line Parameters, parses the given path, and produces an XML file listing each folder and file.

I am running the application as a Post Build event in Visual Studio, passing in the $(TargetDir) macro as;

"c:\Program Files\CreateUpdateXML\CreateUpdateXML.exe" "$(TargetDir)\"

You’ll notice the extra trailing backslash after the Macro.

“Visual studio already adds a trailing backslash though!”

Yes… You’re right… However, a trailing backslash followed by quotation marks is converted to an Escape by Windows.

To work around this issue, you must escape the backslash, forcing windows to use it. This is achieved by simply adding another backslash to the path.

Sunday 23 September 2012

WPF MVVM DataGrid Column Collection Binding

In my efforts to “MVVM-ify” my WPF application as much as possible, I ran into the need to Data Bind the Columns Collection of a DataGrid.

After some searching on StackOverflow, I found a neat solution by David Osborn;

http://stackoverflow.com/questions/3065758/wpf-mvvm-datagrid-dynamic-columns

This solution was in C#, so I converted it to VB;

 Imports System.Collections.ObjectModel  
Imports System.Collections.Specialized
Namespace Controls
Public NotInheritable Class DataGridExtension
Private Sub New()
End Sub
Public Shared Function GetColumns(obj As DependencyObject) As ObservableCollection(Of DataGridColumn)
Return DirectCast(obj.GetValue(ColumnsProperty), ObservableCollection(Of DataGridColumn))
End Function
Public Shared Sub SetColumns(obj As DependencyObject, value As ObservableCollection(Of DataGridColumn))
obj.SetValue(ColumnsProperty, value)
End Sub
Public Shared ReadOnly ColumnsProperty As DependencyProperty = DependencyProperty.RegisterAttached("Columns", _
GetType(ObservableCollection(Of DataGridColumn)), _
GetType(DataGridExtension), _
New UIPropertyMetadata(New ObservableCollection(Of DataGridColumn)(), AddressOf OnDataGridColumnsPropertyChanged))
Private Shared Sub OnDataGridColumnsPropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If d.GetType = GetType(DataGrid) Then
Dim myGrid As DataGrid = TryCast(d, DataGrid)
Dim Columns As ObservableCollection(Of DataGridColumn) = DirectCast(e.NewValue, ObservableCollection(Of DataGridColumn))
If Columns IsNot Nothing Then
myGrid.Columns.Clear()
If Columns IsNot Nothing AndAlso Columns.Count > 0 Then
For Each dataGridColumn As DataGridColumn In Columns
myGrid.Columns.Add(dataGridColumn)
Next
End If
AddHandler Columns.CollectionChanged, Sub(sender As Object, args As NotifyCollectionChangedEventArgs)
If args.NewItems IsNot Nothing Then
For Each column As DataGridColumn In args.NewItems.Cast(Of DataGridColumn)()
myGrid.Columns.Add(column)
Next
End If
If args.OldItems IsNot Nothing Then
For Each column As DataGridColumn In args.OldItems.Cast(Of DataGridColumn)()
myGrid.Columns.Remove(column)
Next
End If
End Sub
End If
End If
End Sub
End Class
End Namespace

Then, in order to use the above, make sure you include the namespace in your XAML resources, for instance;


 xmlns:w="clr-namespace:MyApplication1.Controls"  


Then add the following to your DataGrid;


 w:DataGridExtension.Columns="{Binding Path=DataContext.DataGridColumns, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"  


Also, make sure you set AutoGenerateColumns to False.


Then go ahead and add you property in your View Model;

     ''' <summary>  
''' The DataGrid Columns
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Property DataGridColumns As ObservableCollection(Of DataGridColumn) Implements Interfaces.iMasterDetailsViewModel(Of T).DataGridColumns
Get
Return _DataGridColumns
End Get
Set(value As ObservableCollection(Of DataGridColumn))
If value.Equals(_DataGridColumns) = False Then
_DataGridColumns = value
OnPropertyChanged("DataGridColumns")
End If
End Set
End Property

You can then add a bunch of columns as follows;

 Dim cols As New ObservableCollection(Of DataGridColumn)  
Dim col As New DataGridTextColumn
cols.Add(New DataGridTextColumn With {.Binding = New System.Windows.Data.Binding("Customer_Code"), .Header = "Customer Code", .Width = 100})
cols.Add(New DataGridTextColumn With {.Binding = New System.Windows.Data.Binding("Name"), .Header = "Name", .Width = 250})
DataGridColumns = cols

Seems like quite a nice neat solution.


However, do bear in mind that, as Rakshit Bakshi points out in the answer;



“The above code will do the work in general case. However, it will fail when you hide the datagrid and make it visible again. Because the columns property will show that there are 0 columns when they are hidden and as the columns property is changed, the callback will be fired and it will try to add the columns again, but physically columns does exist in the datagrid so the code will fail with an exception saying can't add duplicate columns.”

Friday 21 September 2012

MVVM: Binding a Linq Query result to a DataGrids’ ItemSource doesn’t update the UI

While creating Desktop Applications using WPF, I always employ the MVVM model if I can. This model is not only great at developing testable n-tier applications, but of course is also highly recommended by Microsoft.

However, as with most software development, there are nearly always specific methods to employ for certain processes and functions.

One of these is while Binding Linq Queries, through a bound property in your ViewModel.

Linq queries as standard return an IEnumerable type. This is fine for the most part, and fits nicely with MVVM, allowing Where statements etc.

However, if you then bind this IEnumerable or List type to a DataGrid, through a property in your ViewModel, and try adding and removing items using the .Add and .Remove extension methods, it becomes apparent that the UI isn’t updating.

This is because the Dot Net Framework has performed certain optimisations, which unfortunately cause it to miss the Add and Remove changes when dealing with IEnumerable and List type objects.

One way around this is to completely break the MVVM model and call the Items.Refresh method on the UI DataGrid. However, this is very very bad practise, and circumnavigates the entire purpose for using MVVM in the first place.

The correct solution is to replace the IEnumerable(of T) or List(of T) with an ObservableCollection(of T).

The Observable collection will automatically notify the UI through the ViewModel, whenever an Add or Remove is called on the collection.

Do remember to import the System.Collections.ObjectModel namespace of course!