Refresh TreeView CollectionViewSource ObservableCollection Item Changed - wpf

Hello I have the following setup for a treeview:
<local:BuddyManager x:Key="bmBuddyManager" />
<CollectionViewSource x:Key="cvsBuddyManager"
Source="{Binding Source={StaticResource bmBuddyManager}, Path=Buddies}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="State" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate x:Key="dtBuddyTemplate" DataType="{x:Type local:Buddy}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Nick}" FontSize="12" FontWeight="Bold" />
<TextBlock Text="{Binding GameHost}" FontSize="12" FontWeight="Bold"
Foreground="Purple" Margin="10,0,0,0" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="hdtBuddyCategoryTemplate" ItemsSource="{Binding Path=Items}"
ItemTemplate="{StaticResource dtBuddyTemplate}">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" Foreground="Gold" FontSize="15" />
</HierarchicalDataTemplate>
<TreeView ItemsSource="{Binding Source={StaticResource cvsBuddyManager}, Path=Groups}"
ItemTemplate="{StaticResource hdtBuddyCategoryTemplate}"
ContextMenuOpening="tvBuddies_ContextMenuOpening"
ContextMenuClosing="tvBuddies_ContextMenuClosing"
Background="Transparent" Margin="2,0,3,3">
</TreeView>
Code Behind:
<System.Runtime.InteropServices.ComVisible(False)> Public Enum BuddyState
Online
Offline
Blocked
End Enum
<System.Runtime.InteropServices.ComVisible(False)> Public Class Buddy
Implements INotifyPropertyChanged
Private _Nick As String
Private _IsInGame As Boolean
Private _GameHost As String
Private _State As BuddyState
Sub New(ByVal xwisNick As String)
_Nick = xwisNick
_State = BuddyState.Offline
End Sub
Sub New(ByVal xwisNick As String, ByVal state As BuddyState)
_Nick = xwisNick
_State = state
End Sub
Public Property Nick() As String
Get
Return _Nick
End Get
Set(ByVal value As String)
_Nick = value
End Set
End Property
Public Property IsInGame() As Boolean
Get
Return _IsInGame
End Get
Set(ByVal value As Boolean)
_IsInGame = value
If _IsInGame = False Then
GameHost = Nothing
End If
OnPropertyChanged("IsInGame")
End Set
End Property
Public Property GameHost() As String
Get
Return _GameHost
End Get
Set(ByVal value As String)
_GameHost = value
OnPropertyChanged("GameHost")
End Set
End Property
Public Property State() As BuddyState
Get
Return _State
End Get
Set(ByVal value As BuddyState)
_State = value
If value = BuddyState.Online Then
If _IsInGame Then
_IsInGame = False
_GameHost = Nothing
End If
End If
OnPropertyChanged("State")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
' Create the OnPropertyChanged method to raise the event
Protected Sub OnPropertyChanged(ByVal name As String)
Try
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
Catch
End Try
End Sub
End Class
Public Class BuddyManager
Implements INotifyPropertyChanged
Private ocBuddies As ObservableCollection(Of Buddy) = New ObservableCollection(Of Buddy)
Public ReadOnly Property Buddies As ObservableCollection(Of Buddy)
Get
Return ocBuddies
End Get
End Property
Public BuddyCheck As List(Of Buddy) = New List(Of Buddy)
Public IsCheckingForBuddies As Boolean = False
Public Function IsBuddy(ByVal XwisNick As String) As Boolean
Dim nick As String = XwisNick.ToLower
For Each b As Buddy In ocBuddies
If b.Nick = nick Then
Return True
End If
Next
Return False
End Function
Public Function IsInGame(ByVal XwisNick As String) As String
Dim nick As String = XwisNick.ToLower
For Each b As Buddy In ocBuddies
If b.Nick = nick Then
If b.IsInGame Then
Return b.GameHost
Else
Return Nothing
End If
End If
Next
Return Nothing
End Function
Public Function AddBuddy(ByVal XwisNick As String) As Boolean
Dim nick As String = XwisNick.ToLower
For Each b As Buddy In ocBuddies
If b.Nick = nick Then
Return False
End If
Next
ocBuddies.Add(New Buddy(nick))
OnPropertyChanged("Buddies")
Return True
End Function
Public Function RemoveBuddy(ByVal XwisNick As String) As Boolean
Dim nick As String = XwisNick.ToLower
For i As Integer = 0 To ocBuddies.Count - 1
If ocBuddies(i).Nick = nick Then
ocBuddies.RemoveAt(i)
OnPropertyChanged("Buddies")
Return True
End If
Next
Return False
End Function
Public Function BlockBuddy(ByVal XwisNick As String) As Boolean
Dim nick As String = XwisNick.ToLower
For i As Integer = 0 To ocBuddies.Count - 1
If ocBuddies(i).Nick = nick Then
ocBuddies(i).State = BuddyState.Blocked
OnPropertyChanged("Buddies")
Return True
End If
Next
ocBuddies.Add(New Buddy(nick, BuddyState.Blocked))
OnPropertyChanged("Buddies")
Return True
End Function
Public Function UnblockBuddy(ByVal XwisNick As String) As Boolean
Dim nick As String = XwisNick.ToLower
For i As Integer = 0 To ocBuddies.Count - 1
If ocBuddies(i).Nick = nick Then
ocBuddies(i).State = BuddyState.Offline
OnPropertyChanged("Buddies")
Return True
End If
Next
Return False
End Function
Public Sub UpdateOnlineStatus(ByVal XwisNick As String, ByVal online As Boolean)
Dim nick As String = XwisNick.ToLower
For i As Integer = 0 To ocBuddies.Count - 1
If ocBuddies(i).Nick = nick Then
If online Then
ocBuddies(i).State = BuddyState.Online
Else
ocBuddies(i).State = BuddyState.Offline
End If
OnPropertyChanged("Buddies")
Exit For
End If
Next
RaiseEvent BuddyOnlineStatusChanged(nick, online)
End Sub
Public Sub UpdateInGameStatus(ByVal XwisNick As String, ByVal gamehost As String)
Dim nick As String = XwisNick.ToLower
For i As Integer = 0 To ocBuddies.Count - 1
If ocBuddies(i).Nick = nick Then
ocBuddies(i).IsInGame = True
ocBuddies(i).GameHost = gamehost
OnPropertyChanged("Buddies")
RaiseEvent BuddyGameStatusChanged(nick, gamehost)
Exit For
End If
Next
End Sub
Public Sub FillBuddyCheck()
BuddyCheck = ocBuddies.Where(Function(bud) bud.State <> BuddyState.Blocked).ToList
End Sub
Public Function GetBuddies() As IEnumerable(Of Buddy)
Return ocBuddies.Where(Function(bud) bud.State <> BuddyState.Blocked)
End Function
Public Sub Sort()
ocBuddies.OrderBy(Function(bud) bud.Nick)
OnPropertyChanged("Buddies")
End Sub
Public Function Count() As Integer
Return GetBuddies.Count
End Function
Public Event BuddyOnlineStatusChanged(ByVal nick As String, ByVal online As Boolean)
Public Event BuddyGameStatusChanged(ByVal nick As String, ByVal gamehost As String)
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
' Create the OnPropertyChanged method to raise the event
Protected Sub OnPropertyChanged(ByVal name As String)
Try
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
Catch
End Try
End Sub
End Class
How I interact with the classes:
Public Function GetBuddyManager() As BuddyManager
Try
Return DirectCast(FindResource("bmBuddyManager"), BuddyManager)
Catch ex As Exception
MessageBox.Show("Error getting buddy manager object: " & ex.ToString())
Application.Current.Shutdown()
Return Nothing
End Try
End Function
GetBuddyManager().UpdateOnlineStatus(GetBuddyManager().BuddyCheck(0).Nick, True)
The Binding and grouping work well, the only issue is when I set a particular 'buddy' to online or blocked the child nodes do not move or change.
I am trying to get this to work like an MSN Treeview where people go offline and online.
Any help is appreciated, I have been working on this problem for a month or so with heavy research and no luck.
Thank you for your time.

Looks like you need an OnPropertyChanged event in the State property for VisualColor. The UI does not get a notification that the visual color should be updated. It knows that the State value has changed, but all that means is whatever is bound to the State property gets updated.
I would suggest putting those colors in XAML and writing a DataTrigger on your item, that evaluates the state and changes the color to suit.
Next as for not moving from one status category to another, when you set the state, have you looked at the CollectionViewSource at runtime to see how it is sorting? Are you calling a refresh on the CVS view anywhere?

Related

Enable and disable button

I have a texbox in binding with a property.
<TextBox Name="txtPrice" Grid.Row="0" Grid.Column="2" MaxLength="8" TabIndex="1"
Text="{Binding Price, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True,
StringFormat= '\{0:#,###.##\}', ConverterCulture=fr-FR}" TextWrapping="Wrap"/>
Private Property _Price As Double
Public Property Price As Double
Get
Return Price
End Get
Set(value As Double)
_Price = Double.Parse(value)
OnPropertyChanged("Price")
End Set
End Property
When I type some chars or the textbox is empty, the button Cmd_Insert must not be enabled, but doesn't work.
Why ? (see Function CanCmd_Insert())
Public ReadOnly Property Cmd_Insert As ICommand
Get
If _Cm_Insert Is Nothing Then
_Cm_Insert = New RelayCommand(AddressOf Cmd_InsertExe, AddressOf CanCmd_Insert)
End If
Return _Cm_Insert
End Get
End Property
Private Sub Cmd_InsertExe()
UPDATE_Price()
End Sub
Private Function CanCmd_Insert() As Boolean
If IsNumeric(Price) = False Then
Return False
Else
Return True
End If
End Function
I added TargetNullValue='' and changed your property to nullable. Refer the below code.
<StackPanel>
<TextBox Name="txtPrice" Grid.Row="0" Grid.Column="2" MaxLength="8" TabIndex="1"
Text="{Binding Price, UpdateSourceTrigger=PropertyChanged, TargetNullValue='',
StringFormat= '\{0:#,###.##\}'}" TextWrapping="Wrap" />
<Button Content="Update" Command="{Binding Cmd_Insert }"></Button>
</StackPanel>
Imports GalaSoft.MvvmLight.CommandWpf
Imports System.ComponentModel
Public Class ViewModel
Implements INotifyPropertyChanged
Private Property _Price As Double?
Public Property Price As Double?
Get
Return _Price
End Get
Set(value As Double?)
_Price = value
OnPropertyChanged("Price")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub OnPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Private Property _Cm_Insert As ICommand
Public ReadOnly Property Cmd_Insert As ICommand
Get
If _Cm_Insert Is Nothing Then
_Cm_Insert = New RelayCommand(AddressOf Cmd_InsertExe, AddressOf CanCmd_Insert)
End If
Return _Cm_Insert
End Get
End Property
Private Sub Cmd_InsertExe()
End Sub
Private Function CanCmd_Insert() As Boolean
If IsNumeric(Price) = False Then
Return False
Else
Return True
End If
End Function
End Class

WPF manipulate selected item in treeview

I have a treeview in which there are selectable items, with subitems which may trigger some code action. When the subitem is clicked, the action should be executed, but the parent item has to remain/become the selected item.
The problem I'm facing is that item are not being deselected properly, causing multiple items to be selected in the treeview.
Here's the xaml for the treeview:
<TreeView Name="Treeview1" Style="{StaticResource vcc_Treeview}" >
<TreeView.ItemTemplate >
<HierarchicalDataTemplate ItemsSource="{Binding Children}" >
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImgSrc}" Style="{StaticResource vcc_TreeviewItemImage}" />
<TextBlock Text="{Binding Description}" Style="{StaticResource vcc_TreeviewItemTextblock}" Foreground="{Binding TextColorBrush}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay, Converter={StaticResource clsBindingDebugger}}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
The clsBindingDebugger does nothing but debug print the converted value and pass through the convert- and convertback value.
Next the somewhat shortened version of the TreeviewItem Class
Public Class MyTreeviewItem
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Event MyTreeviewItem_ExpandedChanged(MTI As MyTreeviewItem, IsExpanded As Boolean)
Public Event MyTreeviewItem_SelectedChanged(MTI As MyTreeviewItem, IsSelected As Boolean)
Public Sub New(Optional Level_ As Integer = 1, Optional ByVal Key_ As String = "")
MyBase.New()
Me.Children = New ObservableCollection(Of MyTreeviewItem)
_Key = Key_
_Level = Level_
End Sub
Private _Level As Integer
Public Property Level As Integer
Get
Level = _Level
End Get
Set(value As Integer)
_Level = value
End Set
End Property
Private _Descr As String = ""
Public Property Description As String
Get
Description = _Descr
End Get
Set(value As String)
_Descr = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Description"))
End Set
End Property
Private _Key As String = ""
Public Property Key As String
Get
Key = _Key
End Get
Set(value As String)
_Key = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Key"))
End Set
End Property
Private _ImgSrc As ImageSource
Public Property ImgSrc As ImageSource
Get
ImgSrc = _ImgSrc
End Get
Set(value As ImageSource)
_ImgSrc = value
End Set
End Property
Private _Children As ObservableCollection(Of MyTreeviewItem)
Public Property Children As ObservableCollection(Of MyTreeviewItem)
Get
Return _Children
End Get
Set(value As ObservableCollection(Of MyTreeviewItem))
_Children = value
End Set
End Property
Private _IsSelected As Boolean
Overloads Property IsSelected As Boolean
Get
Return _IsSelected
End Get
Set(ByVal value As Boolean)
_IsSelected = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("IsSelected"))
RaiseEvent MyTreeviewItem_SelectedChanged(Me, _IsSelected)
End Set
End Property
Private _IsActionItem As Boolean
Overloads Property IsActionItem As Boolean
Get
Return _IsActionItem
End Get
Set(ByVal value As Boolean)
_IsActionItem = value
End Set
End Property
End Class
Finally I trigger the event MyTreeviewItem_SelectedChanged:
Private ReselectingParent As Boolean = False
Private Sub MyTreeviewItem_SelectedChanged(MTI As MyTreeviewItem, IsSelected As Boolean)
Debug.Print(MTI.Description & " Selected = " & IsSelected)
If ReselectingParent Then Exit Sub
If IsSelected Then
'Do some (action) stuff here
If MTI.IsActionItem AndAlso MTI.Parent IsNot Nothing Then
ReselectingParent = True 'to prevent this sub from being executed with the next 2 lines
MTI.IsSelected = False
MTI.Parent.IsSelected = True
ReselectingParent = False
End If
End If
End Sub
Suppose the treeview looks like:
item 1
--Action 1
--Action 2
Item 2
--Action 3
--Action 4
In my starting situation item 2 is selected. Now I click Action 1, so the action should be executed and afterwards item 1 should be the selected item.
The debug.print results in these lines:
ConvertBack: False
Item 2 Selected = False
Convert: False
ConvertBack: True
Action 2 Selected = True
Action 2 Selected = False
Convert: True
Item 1 Selected = True
Convert: False
At this point Item 1 is selected, as it should be.
Now I click Action 3.
ConvertBack: False
Action 2 Selected = False
Convert: False
ConvertBack: True
Action 3 Selected = True
Action 3 Selected = False
Convert: True
Item 2 Selected = True
Convert: False
The result is that Item 1 and Item 2 are both selected, where only Item 2 should be selected. The second debugger block says "Action 2 Selected = False", this should be "Item 1 Selected = False"
I hope I'm clear. Can anybody point me to the solution of my problem?
Thanks!
For who's interested...
I stille don't quite understand why the code isn't working properly. But apparently the clicked action-item wasn't deselected properly. Instead of calling these lines:
ReselectingParent = True 'to prevent this sub from being executed with the next 2 lines
MTI.IsSelected = False
MTI.Parent.IsSelected = True
ReselectingParent = False
directly in MyTreeviewItem_SelectedChanged I now call a timer instead. In the timer_elapsed event I call the four lines.
Apparently the primary select of the action-item was not finished when calling the code to select it's parent. It's an ugly workaround, but it works for me...

wpf image button in combobox list

I'm trying to get an image displayed in the list of a combobox based on a bound boolean value. When the image is clicked the boolean value, and thus the image should change.
Here's the xaml:
<ComboBox Name="Combo2" Margin="20,79,20,0" ItemsSource="{Binding}" VerticalAlignment="Top" Height="20">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Name="MyBoolImage" Height="12" Width="12" MouseLeftButtonUp="Image_MouseLeftButtonUp"/>
<TextBlock Text="{Binding name}" Margin="5,0,0,0" Width="100" />
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding LightOn}" Value="False">
<Setter TargetName="MyBoolImage" Property="Source" Value="/Images/Exit.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding LightOn}" Value="True">
<Setter TargetName="MyBoolImage" Property="Source" Value="/Images/Cut.png"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And the data class:
Class ComboData
Private _LightOn As Boolean
Public Property LightOn As Boolean
Get
Return _LightOn
End Get
Set(value As Boolean)
_LightOn = value
End Set
End Property
Private _name As String
Public Property name As String
Get
Return _name
End Get
Set(value As String)
_name = value
End Set
End Property
Sub New(name_ As String, Light_On As Boolean)
_LightOn = Light_On
_name = name_
End Sub
End Class
loading some test data:
Dim x As New List(Of ComboData)
x.Add(New ComboData("test1a", True))
x.Add(New ComboData("test2a", False))
x.Add(New ComboData("test3a", True))
x.Add(New ComboData("test4a", True))
x.Add(New ComboData("test5a", False))
x.Add(New ComboData("test6a", True))
Combo2.ItemsSource = x
and finally the click event where the magic isn't happening...
Private Sub Image_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs)
Dim SelectedComboData As ComboData = TryCast(CType(sender, Image).DataContext, ComboData)
SelectedComboData.LightOn = Not SelectedComboData.LightOn
e.Handled = True
End Sub
The LightOn value is changed as supposed to, even in the "x" (the list of combodata), the value is changed. But the displayed image is not changing.
What am I missing?
Thanks!
Class comboData should implement INotifyPropertyChanged in order to notify UI about its changes..
Class ComboData
Implements INotifyPropertyChanged
Private _LightOn As Boolean
Public Property LightOn As Boolean
Get
Return _LightOn
End Get
Set(value As Boolean)
_LightOn = value
OnPropertyChanged("LightOn")
End Set
End Property
Private _name As String
Public Property name As String
Get
Return _name
End Get
Set(value As String)
_name = value
OnPropertyChanged("name")
End Set
End Property
Sub New(name_ As String, Light_On As Boolean)
_LightOn = Light_On
_name = name_
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class

Filter items in VB.net datagrid in WPF

WPF/VB.net newbie here.
I'm trying to filter rows in a datagrid and having a fun time.
I've managed to create a list of objects and use the itemsource property to get the datagrid to populate.
Now I have a checkbox that for arguments-sake I want to click and filter down only those rows that match this criterion.
With the code below I'm getting the general "Object reference not set to an instance of an object." error but a bit lost. I'm sure a VB pro will see it.
I'd prefer to do more in code, rather than XAML if possible.
This is my XAML:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="displayGrid" HorizontalAlignment="Left" Margin="62,94,0,0" VerticalAlignment="Top" Height="142" Width="360" SelectionChanged="DataGrid_SelectionChanged"/>
<Button Content="Load" HorizontalAlignment="Left" Margin="62,51,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<CheckBox x:Name="showOnlyChildren" Content="Show Only Children" HorizontalAlignment="Left" Margin="172,51,0,0" VerticalAlignment="Top" Width="147"/>
</Grid>
</Window>
And this is my code:
Class MainWindow
Class person
Property name
Property age
End Class
Dim listOfPersons As New List(Of person)
Private filteredList As CollectionViewSource
Private Sub DataGrid_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
Dim aPerson As New person With {
.name = "Fred Smith",
.age = 12}
listOfPersons.Add(aPerson)
Dim bPerson As New person With {
.name = "Tom Jones",
.age = 50}
listOfPersons.Add(bPerson)
displayGrid.ItemsSource = CollectionViewSource.GetDefaultView(listOfPersons)
End Sub
Private Sub ShowOnlyChildrenFilter(ByVal sender As Object, ByVal e As FilterEventArgs)
Dim person As person = TryCast(e.Item, person)
If person IsNot Nothing Then
' Filter out persons with age less than 18
If person.age < 19 Then
e.Accepted = True
Else
e.Accepted = False
End If
End If
End Sub
Private Sub AddFiltering(ByVal sender As Object, ByVal args As RoutedEventArgs) Handles showOnlyChildren.Checked
AddHandler filteredList.Filter, AddressOf ShowOnlyChildrenFilter
End Sub
Private Sub RemoveFiltering(ByVal sender As Object, ByVal args As RoutedEventArgs)
RemoveHandler filteredList.Filter, AddressOf ShowOnlyChildrenFilter
End Sub
End Class
EDIT: OK slowly but surely getting there. I'm incorporated some changes that I found here and thanks to help I got here.... This is what the code looks like now:
Imports System.ComponentModel
Class MainWindow
Class person
Property name
Property age
End Class
Dim listOfPersons As New List(Of person)
Private filteredList As CollectionViewSource
Dim view As ICollectionView
Private Sub DataGrid_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
Dim aPerson As New person With {
.name = "Fred Smith",
.age = 12}
listOfPersons.Add(aPerson)
Dim bPerson As New person With {
.name = "Tom Jones",
.age = 50}
listOfPersons.Add(bPerson)
view = CollectionViewSource.GetDefaultView(listOfPersons)
displayGrid.ItemsSource = view
End Sub
Function ShowOnlyChildrenFilter(ByVal param As Object) As Boolean
Dim person As person = TryCast(param, person)
Dim retValue As Boolean
If person IsNot Nothing Then
' Filter out persons with age less than 18
If person.age < 19 Then
retValue = True
Else
retValue = False
End If
End If
Return retValue
End Function
Private Sub showOnlyChildren_Checked(sender As Object, e As RoutedEventArgs) Handles showOnlyChildren.Checked
If showOnlyChildren.IsChecked = True Then
view.Filter = New Predicate(Of Object)(AddressOf ShowOnlyChildrenFilter)
Else
'what goes here?
End If
End Sub
End Class
The only thing I'm missing is how to refresh the datagrid, when the checkbox is unchecked. Thanks to all. I'm still amazed at how complex and mindbending this is for what I would have thought would be quite simple.
This should be a comment and not an answer but I am unable to comment as not enough rep!
Have you tried using
ICollectionView
I have a C# example I can provide if you are able to convert it!
EDIT:
I thought I would just chuck in the example as it may help a little
private void cbBlahYear_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
lvwMainBlahFilter();
}
private void lvwMainBlahFilter()
{
ICollectionView view = CollectionViewSource.GetDefaultView(lvwMainBlah.ItemsSource);
view.Filter = null;
view.Filter = new Predicate<object>(FilterBlahByYearID);
view.SortDescriptions.Add(new SortDescription("Forename", ListSortDirection.Ascending));
}
private Boolean FilterBlahByYearID(object obj)
{
BlahModel item = obj as BlahModel;
if (item == null) return false;
Int32 myID = 0;
if (cbBlahYear.SelectedItem != null)
{
YearModel year = cbBlahYear.SelectedItem as YearModel;
myID = year.id;
}
if (myID == 0) return false;
if (item.YearID == myID) return true;
return false;
}
The variable filteredList that you are tring to:
AddHandler filteredList.Filter, AddressOf ShowOnlyChildrenFilter
Is still nothing at this line. Try declaring it as NEW first:
Private filteredList As New CollectionV
OK It is done. Made a couple more newbie errors on the above code. So here is the working version. Tick a checkbox and it filters on a condition.
VB code:
Imports System.ComponentModel
Class MainWindow
Class person
Property name
Property age
End Class
Dim listOfPersons As New List(Of person)
Dim view As ICollectionView
Private Sub DataGrid_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
MsgBox("changed")
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
Dim aPerson As New person With {
.name = "Fred Smith",
.age = 12}
listOfPersons.Add(aPerson)
Dim bPerson As New person With {
.name = "Tom Jones",
.age = 50}
listOfPersons.Add(bPerson)
view = CollectionViewSource.GetDefaultView(listOfPersons)
displayGrid.ItemsSource = view
End Sub
Function ShowOnlyChildrenFilter(ByVal param As Object) As Boolean
Dim person As person = TryCast(param, person)
Dim retValue As Boolean
If person IsNot Nothing Then
' Filter out persons with age less than 18
If person.age < 19 Then
retValue = True
Else
retValue = False
End If
End If
Return retValue
End Function
Private Sub showOnlyChildren_Checked(sender As Object, e As RoutedEventArgs) Handles showOnlyChildren.Checked
If showOnlyChildren.IsChecked = True Then
view.Filter = New Predicate(Of Object)(AddressOf ShowOnlyChildrenFilter)
End If
End Sub
Private Sub showOnlyChildren_UnChecked(sender As Object, e As RoutedEventArgs) Handles showOnlyChildren.Unchecked
If showOnlyChildren.IsChecked = False Then
view.Filter = Nothing
End If
End Sub
End Class
With XAML:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="displayGrid" HorizontalAlignment="Left" Margin="62,94,0,0" VerticalAlignment="Top" Height="142" Width="360" SelectionChanged="DataGrid_SelectionChanged"/>
<Button Content="Load" HorizontalAlignment="Left" Margin="62,51,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<CheckBox x:Name="showOnlyChildren" Content="Show Only Children" HorizontalAlignment="Left" Margin="172,51,0,0" VerticalAlignment="Top" Width="147"/>
</Grid>
</Window>

Async Update ItemsControl Inside of a Datagrid (or suggest a better way)

I have a datagrid bound to an observable collection. Each item inside of the grid can have multiple detail lines, which are stored in an observable collection property inside of the main object.
The line details are being fetched from a linked server with a slow connection, so I'd like to put a background worker in to update the line details OC, but I get an error saying that the control can't be updated outside of the thread that made it.
What's the best way to do this. The 2 second lag is a bit much.
datagrid xaml:
<DataGrid AutoGenerateColumns="False" Name="dgROList" ItemsSource="{Binding ElementName=MainWindow, Path=cROInfo}" CanUserDeleteRows="True" CanUserReorderColumns="False" GridLinesVisibility="Horizontal" Margin="0,112,0,0" Grid.ColumnSpan="2" AlternatingRowBackground="#FFFFE776">
<DataGrid.Columns>
<DataGridTextColumn Header="RO Number" Width="Auto" Binding="{Binding RONum}" />
<DataGridTemplateColumn x:Name="roDetails" Header="RO Details" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ItemsControl Name="LineDetails" ItemsSource="{Binding LineInfo}" Width="Auto">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<ItemsPresenter />
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Line}" />
<Label Content="{Binding Status}" />
<Label Content="{Binding PaidAmount}" />
<Label Content="{Binding SDate}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--<DataGridTextColumn Header="RO Details" Width="*" Binding="{Binding RODetails}" />-->
</DataGrid.Columns>
</DataGrid>
OC class:
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Public Class ocROInformation
Implements INotifyPropertyChanged
Private _RONum As String
Private _LineInfo As ObservableCollection(Of ocROLineInformation)
Private _Changed As Boolean
Private _RONumChanged As Boolean
Private _RODetailsChanged As Boolean
Private WithEvents bgworker As BackgroundWorker
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Sub New(ronum As String)
_RONum = ronum
_LineInfo = New ObservableCollection(Of ocROLineInformation)
bgworker = New BackgroundWorker
bgworker.RunWorkerAsync()
' GetData() ' If I call this directly it works, just lags out while the query runs for a little bit.
End Sub
Protected Overridable Sub OnPropertyChanged(ByVal Propertyname As String)
If Not Propertyname.Contains("Changed") Then
Changed = True
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Propertyname))
End Sub
Sub GetData() Handles bgworker.DoWork
Dim DCodes As String = DealerCodes
Dim rSelect As New ADODB.Recordset
Dim sSql As String = "SELECT DISTINCT * FROM IGlobal WHERE RMAJBC = " & _RONum"
Dim line As Integer
Dim status As String = ""
Dim sdate As Date
Dim paidamount As Double
Dim tdate As String
With rSelect
.Open(sSql, MyCn, ADODB.CursorTypeEnum.adOpenStatic, ADODB.LockTypeEnum.adLockReadOnly)
If .EOF Then
status = "Never Received"
End If
Do While Not .EOF
line = .Fields!LineNum.Value
status = NZ(.Fields!Stat6.Value, "")
paidamount = NZ(.Fields!PaidAmount.Value, 0)
tdate = NZ(.Fields!SDate.Value, "")
If Not tdate = "" And Not tdate = "0" Then
sdate = Date.ParseExact(tdate, "yyyyMMdd", System.Globalization.DateTimeFormatInfo.InvariantInfo)
_LineInfo.Add(New ocROLineInformation(line, status, sdate, paidamount))
Else
_LineInfo.Add(New ocROLineInformation(line, status))
End If
.MoveNext()
Loop
.Close()
End With
OnPropertyChanged("RODetails")
End Sub
Public Property Changed() As Boolean
Get
Return _Changed
End Get
Set(ByVal value As Boolean)
If _Changed <> value Then
_Changed = value
OnPropertyChanged("Changed")
End If
End Set
End Property
Public Property RONum() As String
Get
Return _RONum
End Get
Set(value As String)
If _RONum <> value Then
_RONum = value
RONumChanged = True
OnPropertyChanged("RONum")
GetData()
End If
End Set
End Property
Public ReadOnly Property RODetails As String
Get
Dim output As String = ""
For Each l As ocROLineInformation In _LineInfo
output &= l.Print & " "
Next
Return output '"This is a test: " & _RONum
End Get
End Property
Public ReadOnly Property LineInfo As ObservableCollection(Of ocROLineInformation)
Get
Return _LineInfo
End Get
End Property
Public Property RODetailsChanged As Boolean
Get
Return _RODetailsChanged
End Get
Set(value As Boolean)
If _RODetailsChanged <> value Then
_RODetailsChanged = value
OnPropertyChanged("RODetailsChanged")
End If
End Set
End Property
Public Property RONumChanged() As Boolean
Get
Return _RONumChanged
End Get
Set(value As Boolean)
If _RONumChanged <> value Then
_RONumChanged = value
OnPropertyChanged("RONumChanged")
End If
End Set
End Property
End Class
Public Class ocROLineInformation
Implements INotifyPropertyChanged
Private _Line As Integer
Private _Status As String
Private _SDate As Date
Private _PaidAmount As Double
Private _Changed As Boolean
Private _LineChanged As Boolean
Private _StatusChanged As Boolean
Private _SDateChanged As Boolean
Private _PaidAmountChanged As Boolean
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Sub New(line As Integer, status As String, sdate As Date, paidamount As Double)
_Line = line
_Status = status
_SDate = sdate
_PaidAmount = paidamount
End Sub
Public Sub New(line As Integer, status As String)
_Line = line
_Status = status
End Sub
Public ReadOnly Property Print() As String
Get
Return "Line: " & _Line & ", Status: " & _Status & ", Amount: " & _PaidAmount & ", Date: " & _SDate.ToShortDateString
End Get
End Property
Protected Overridable Sub OnPropertyChanged(ByVal Propertyname As String)
If Not Propertyname.Contains("Changed") Then
Changed = True
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Propertyname))
End Sub
Public Property Changed() As Boolean
Get
Return _Changed
End Get
Set(ByVal value As Boolean)
If _Changed <> value Then
_Changed = value
OnPropertyChanged("Changed")
End If
End Set
End Property
Public Property Line() As Integer
Get
Return _Line
End Get
Set(value As Integer)
If _Line <> value Then
_Line = value
LineChanged = True
OnPropertyChanged("Line")
End If
End Set
End Property
Public Property Status() As String
Get
Return _Status
End Get
Set(value As String)
If _Status <> value Then
_Status = value
StatusChanged = True
OnPropertyChanged("Status")
End If
End Set
End Property
Public Property SDate() As Date
Get
Return _SDate
End Get
Set(value As Date)
If _SDate <> value Then
_SDate = value
SDateChanged = True
OnPropertyChanged("SDate")
End If
End Set
End Property
Private Property PaidAmount() As Double
Get
Return _PaidAmount
End Get
Set(value As Double)
If _PaidAmount <> value Then
_PaidAmount = value
PaidAmountChanged = True
OnPropertyChanged("PaidAmount")
End If
End Set
End Property
Public Property LineChanged() As Boolean
Get
Return _LineChanged
End Get
Set(value As Boolean)
If _LineChanged <> value Then
_LineChanged = value
OnPropertyChanged("LineChanged")
End If
End Set
End Property
Public Property StatusChanged() As Boolean
Get
Return _StatusChanged
End Get
Set(value As Boolean)
If _StatusChanged <> value Then
_StatusChanged = value
OnPropertyChanged("StatusChanged")
End If
End Set
End Property
Public Property SDateChanged() As Boolean
Get
Return _SDateChanged
End Get
Set(value As Boolean)
If _SDateChanged <> value Then
_SDateChanged = value
OnPropertyChanged("SDateChanged")
End If
End Set
End Property
Public Property PaidAmountChanged() As Boolean
Get
Return _PaidAmountChanged
End Get
Set(value As Boolean)
If _PaidAmountChanged <> value Then
_PaidAmountChanged = value
OnPropertyChanged("PaidAmountChanged")
End If
End Set
End Property
End Class
You need to update the collection on the UI thread. To do this make sure to pass the data back via the Result property on the DoWorkEventArgs.
In your completed event you can then run through the data via the RunWorkerCompletedEventArgs.Result property and set your ObservableCollection accordingly, all on the UI thread.
If you wanted to avoid the BackgroundWorker you could use the Dispatcher.
ThreadStart start = delegate()
{
// make your calls to the db
Dispatcher.Invoke(DispatcherPriority.Normal,
new Action<object>(UpdateCollection),
new object[] { myData });
};
new Thread(start).Start();
private void UpdateCollection(object data)
{
//iterate your collection and add the data as needed
}
Whatever route you go, the root cause is attempting to access an object created on the UI thread from a differing thread.

Resources