I am novice in WPF. I have a wpftoolkit datagrid where i am using a combo box as datagridcombox column. I am using a observable collection of Codes for binding the combo box. Below is the collection and its class...
#Region "Class & Coll"
Public Class CodesColl
Inherits ObservableCollection(Of Codes)
End Class
Public Class Codes
Private pCode As String
Private pDescription As String
Public Sub New()
pCode = String.Empty
pDescription = String.Empty
End Sub
#End Region
#Region "Property"
Public Property fldCode() As String
Get
Return pCode
End Get
Set(ByVal value As String)
pCode = value
End Set
End Property
Public Property fldDescription() As String
Get
Return pDescription
End Get
Set(ByVal value As String)
pDescription = value
End Set
End Property
#End Region
End Class
Now what i want achieve is that i need to bind the collection with dropdown in the grid.In my grid i have two columns in first column i have to display the code (fldCode) , and on the selection of the code the next column of the same row will get populated with its description (fldDescription).
My Xaml is something like this:
<wpfkit:DataGrid Margin="3" Style="{DynamicResource SimpleDataGrid}" FontWeight="Normal"
MaxHeight="100" ItemsSource="{Binding Source={StaticResource odpExistingCodesColl}}"
AutoGenerateColumns="False" Name="dgCodes" VerticalScrollBarVisibility="Visible" >
<wpfkit:DataGrid.Columns>
<wpfkit:DataGridTemplateColumn IsReadOnly="True">
<wpfkit:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Style="{DynamicResource SimpleImageDelete}"/>
</DataTemplate>
</wpfkit:DataGridTemplateColumn.CellTemplate>
</wpfkit:DataGridTemplateColumn>
<wpfkit:DataGridComboBoxColumn Header="Code"
DisplayMemberPath="fldCode"
SelectedValueBinding="{Binding fldCodes.fldCode}"
SelectedValuePath="fldCode"
SelectedItemBinding="{Binding fldCodeList}"
Width="100" x:Name="cbTCodes" >
<wpfkit:DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="IsSynchronizedWithCurrentItem" Value="False" />
<Setter Property ="ItemsSource" Value="{Binding Path=odpCodesColl}"/>
</Style>
</wpfkit:DataGridComboBoxColumn.ElementStyle>
<wpfkit:DataGridComboBoxColumn.EditingElementStyle >
<Style TargetType="ComboBox">
<Setter Property ="ItemsSource" Value="{Binding Path=odpCodesColl}"/>
<Setter Property ="IsDropDownOpen" Value="True"/>
</Style>
</wpfkit:DataGridComboBoxColumn.EditingElementStyle>
</wpfkit:DataGridComboBoxColumn>
<wpfkit:DataGridTextColumn Width="375" Header="Description" x:Name="tbTCodeDescription" />
</wpfkit:DataGrid.Columns>
</wpfkit:DataGrid>
odpExistingCodesColl here is another collection through which i am binding the entire grid and is used for sending the code and its description to but i am facing following problems
Unable to display the codes in dropdown.
Somehow i manged to do so but it disappears after loosing focus from the combobox.
Unable to retrive the description on its selection change as , i am unable to find the event too.
So you guys are requested to help me out asap.. any help will be highly appreciated..
Thanks in Advance
Amit Ranjan
You can check on Vincent's blog for detail information about how to work with Wpf DataGrid(DataGridComboBoxColumn too).
Related
I'm attempting to implement a value converter that will change the color of a button on a user control based on the custom "MyUserControlStatus" property of the user control "MyUserControl".
The code-behind looks like this:
Public Class MyUserControl
Public Property MyUserControlStatus As Integer = 0
End Class
Public Class StatusIndicatorConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
Dim indicatorbrush As Brush = Brushes.Transparent
Dim status As Integer = CType(value, Integer)
Select Case status
Case 0 : indicatorbrush = Brushes.Red
Case 1: indicatorbrush = Brushes.Green
Case 2 : indicatorbrush = Brushes.Yellow
End Select
Return indicatorbrush
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class
The XAML looks like this:
<UserControl x:Class="MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MainApplicationName"
xmlns:wpf="clr-namespace:LibVLCSharp.WPF;assembly=LibVLCSharp.WPF"
mc:Ignorable="d"
>
<UserControl.Resources>
<local:StatusIndicatorConverter x:Key="MyStatusIndicatorConverter"/>
</UserControl.Resources>
<Button x:Name="ButtonStatusIndicator"
Background="{Binding Path=MyUserControlStatus, Converter={StaticResource MyStatusIndicatorConverter}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
>
<Button.Resources>
<Style TargetType="Border">
<Setter Property="Height" Value="10"/>
<Setter Property="Width" Value="10"/>
<Setter Property="CornerRadius" Value="10"/>
</Style>
</Button.Resources>
</Button>
</UserControl>
I get an empty, non-colored status. The breakpoints within the Convert function don't fire. I'm not sure what I'm doing wrong here, as all the parts seem to be in place. It should have made the button indicator red (for value=0). The expectation is that, as the MyUserControlStatus changes, the color of the indicator button will change, too, as it is bound to MyUserControlStatus and that value is converted to a color.
A Binding in a UserControl's XAML to one of its own properties must explicitly set the source object of the Binding, e.g. by setting the RelativeSource property.
Setting Mode=TwoWay and UpdateSourceTrigger=PropertyChanged is however pointless. It has no effect at all in this Binding.
Background="{Binding Path=MyUserControlStatus,
RelativeSource={RelativeSource AncestorType=UserControl},
Converter={StaticResource MyStatusIndicatorConverter}}"
Besides that, the source property must fire a change notification in order to have the binding target property updated.
It should be implemented as a dependency property:
Public Shared ReadOnly MyUserControlStatusProperty As DependencyProperty =
DependencyProperty.Register(
name:="MyUserControlStatus",
propertyType:=GetType(Integer),
ownerType:=GetType(MyUserControl))
Public Property MyUserControlStatus As Integer
Get
Return CType(GetValue(MyUserControlStatusProperty), Integer)
End Get
Set
SetValue(MyUserControlStatusProperty, Value)
End Set
<ItemsControl ItemsSource="{Binding ExportFormat, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" Margin="5" Height="50" Width="70" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.setExportFormat, UpdateSourceTrigger=PropertyChanged}" CommandParameter="{Binding}"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
So i have that code in my xaml and the button gets filed with a list of string. Depending on what the users chooses on previous Usercontrol that item will be filed with differents items. The problem is if the user select one option at the first run the button will be filled correctly but if the user go back and select the other option the control doenst update and shows the same as before..
My english is not the best but i think i could made me understand! Any idea?!
PS: the bindind on Button is to a readOnly property so i cant define it to Mode="TwoWay".. i took a look on the debug and the property ExportFormat gets updates with the new items but the usercontrol still displays the first option!!
Sincerely Rui Nunes
You didn't provide code-behind so I'm gonna take a few shots in the dark here:
The ExportFormatcollection is not an ObservableCollection (or more generally, doesn't implement INotifyCollectionChanged).
If it actually is an ObservableCollection, you assign it directly, instead of clearing its items and adding the new ones. example:
ExportFormat = MyNewObsCollection; //Bad
ExportFormat.Clear();
foreach(var newItem in myNewObsCollection)
{
ExportFormat.Add(newItem); //Good
}
Side note: ExportFormat should be readonly
Thanks to #Baboon for giving me some lights on this problem. So the Solution to my problem is:
So my ExportFormat Property was defined as:
Private _ExportFormat As New List(Of String)
Public Property ExportFormat As List(Of String)
Get
Return _ExportFormat
End Get
Set(value As List(Of String))
_ExportFormat = value
NotifyPropertyChanged("ExportFormat")
End Set
End Property
and i just had to change the List(of String) to ObjectModel.ObservableCollection(Of String)..
Private _ExportFormat As New ObjectModel.ObservableCollection(Of String)
Public Property ExportFormat As ObjectModel.ObservableCollection(Of String)
Get
Return _ExportFormat
End Get
Set(value As ObjectModel.ObservableCollection(Of String))
_ExportFormat = value
NotifyPropertyChanged("ExportFormat")
End Set
End Property
And my problems got solved.. Thanks once again!
I have 2 templates for DataGrid's CellTemplate. When I change the items, it won't help me select the template for me, my DisplayModeTemplateSelector won't even be called!
What I'm wondering is if there is a way to trigger this CellTemplateSelector again when items changed? How to refresh CellTemplate in DataGrid or ListView When Content Changes
<DataGridTemplateColumn x:Name="colorRange"
Width="*"
Header="Color Range">
<DataGridTemplateColumn.CellTemplateSelector>
<local:DisplayModeTemplateSelector HeatMapTemplate="{StaticResource heatMapTemplate}" ThreshHoldTemplate="{StaticResource threshHoldTemplate}" />
</DataGridTemplateColumn.CellTemplateSelector>
</DataGridTemplateColumn>
I found this blog
http://dotdotnet.blogspot.com/2008/11/refresh-celltemplate-in-listview-when.html
I think this is similar with my problem, but I really can't understand him! Can anyone explain it?
The solution in the blog post will not work with the DataGrid control because the DataGridTemplateColumn class doesn't belong to the Visual Tree, and even when I tried to bind it to a static class, I didn't suceed because of strange exceptions after property changes.
Anyway there is two possible ways to solve this problem.
1) The easier way.
Using the ObservableCollection class.
var itemIndex = 0;
var currentItem = vm.Items[itemIndex];
//Change necessary properties
//..
vm.Items.Remove(currentItem);
vm.Items.Insert(itemIndex, currentItem);
2) The more complex way.
You can add to your item class the property which returns the object itself.
public ItemViewModel(/*...*/)
{
this.SelfProperty = this;
//...
}
public ItemViewModel SelfProperty { get; private set; }
public void Update()
{
this.SelfProperty = null;
this.OnPropertyChanged("SelfProperty");
this.SelfProperty = this;
this.OnPropertyChanged("SelfProperty");
}
After that you can use the ContentControl.ContentTemplateSelector instead of the CellTemplateSelector like this:
<DataGridTemplateColumn Header="Color Range">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding SelfProperty}" ContentTemplateSelector="{StaticResource mySelector}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
And when you change the property, call the Update method somehow:
currentItem.SomeDataProperty = "some new value";
//Or you can add this method call to the OnPropertyChanged
//so that it calls authomatically
currentItem.Update();
The reason why I've set a null value to the SelfProperty in the Update method first, is that the Selector will not update a template until the Content property is completely changed. If I set the same object once again - nothing will happen, but if I set a null value to it first - changes will be handled.
The easy way is to hook the Combo Box's Selection Changed event, and reassign the template selector. This forces a refresh.
In XAML (assume the rest of the DataGrid/ComboBoxColumn:
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.Gates, UpdateSourceTrigger=PropertyChanged}"/>
<EventSetter Event="SelectionChanged" Handler="GateIDChanged" />
</Style>
That refers to this DataGridTemplateColumn:
<DataGridTemplateColumn x:Name="GateParamsColumn" Header="Gate Parameters" CellTemplateSelector="{StaticResource GateParamsTemplateSelector}"></DataGridTemplateColumn>
And in the code behind:
private void GateIDChanged(object sender, SelectionChangedEventArgs eventArgs)
{
var selector = GateParamsColumn.CellTemplateSelector;
GateParamsColumn.CellTemplateSelector = null;
GateParamsColumn.CellTemplateSelector = selector;
}
I'm using a ListCollectionView as an ItemsSource for a WPF DataGrid.
I want the user to be able to add columns to group by, and I'm using the following as the GroupStyle:
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Group Name: "/>
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding Path=ItemCount}" FontStyle="Italic"/>
<TextBlock Text=" Items" FontStyle="Italic"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter>
</ItemsPresenter>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
My problem is that when there is more then 1 column in the GroupDescriptions, then group headings are displayed without indentation. Another problem is that I would like the TextBox that has "Group Name:" to bind to the Column name that is grouping that level - So if I'm groupint at that level on column = Gender it would say "Gender: ".
So how can I indent the group heading according to its nesting level in the GroupDescriptions collection, and how can I bind to the Column name?
No one stepped up to this, so with a lot of tinkering, I came up with the following solution. I created a multi value converter, taking the current CollectionViewGroup, the whole ListCollectionView and the DataGrid as parameters.
Public Class GroupLevelConverter
Implements IMultiValueConverter
Public Function Convert(values() As Object, targetType As System.Type,
parameter As Object, culture As System.Globalization.CultureInfo
) As Object Implements System.Windows.Data.IMultiValueConverter.Convert
If TypeOf values(0) Is CollectionViewGroup Then
Dim level As Integer = 0
Dim parent As CollectionViewGroup = values(0)
Do While parent IsNot Nothing
parent = GetParent(values(0))
values(0) = parent
If parent IsNot Nothing Then
level += 1
End If
Loop
Dim s As String = ""
For i = 1 To level - 1
s += " "
Next
Dim lcv As ListCollectionView = DirectCast(values(1), ListCollectionView)
Dim pgd As System.Windows.Data.PropertyGroupDescription = lcv.GroupDescriptions(level - 1)
Dim dg As DataGrid = values(2)
Dim GroupHeader As String = pgd.PropertyName
For c = 0 To dg.Columns.Count - 1
If dg.Columns(c).SortMemberPath = GroupHeader Then
GroupHeader = dg.Columns(c).Header
Exit For
End If
Next
s = s & GroupHeader & ": "
Return (s)
End If
Return ""
End Function
Public Function ConvertBack(value As Object, targetTypes() As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object() Implements System.Windows.Data.IMultiValueConverter.ConvertBack
Throw New NotSupportedException("Not implemented")
End Function
Friend Function GetParent(currentViewGroup As CollectionViewGroup) As CollectionViewGroup
Dim parent As CollectionViewGroup
Try
parent = TryCast(currentViewGroup.[GetType]().GetProperty("Parent", System.Reflection.BindingFlags.GetProperty Or
System.Reflection.BindingFlags.Instance Or
System.Reflection.BindingFlags.NonPublic).GetValue(currentViewGroup, Nothing),
CollectionViewGroup)
Catch ex As Exception
Return Nothing
End Try
Return parent
End Function
End Class
The CollectionViewGroup is used to find its parent with the GetParent function. This is the non-elegant part of the solution as it relies on capturing an error. The function is called until the error indicates reaching the top group.
ListCollectionView is used to get the sort column, and the DataGrid to get the more friendly Column Header.
HTH someone looking at a similar problem.
I've encountered a problem when converting a WPF project from vs2008 to vs2010.
I have a DataGrid that contains a ListBox. Each ListBoxItem has a Label and a Button. After converting to vs2010 the button no longer renders but crashes the app as soon as it comes into view. (Ie. the app loads but when the ListBox is created I get a NullReferenceException. What does work though is to remove the click event from the button and then it renders fine :) Same type of setup with Button within ListBoxItem also works when not inside a DataGrid. The content of ListBox obviously is meant to be dynamic but when working with a static collection I get the same error. Also removing the CommandParam does not help at all. Any pointers most welcome.
Code:
<DataGrid x:Name="DgTest" AutoGenerateColumns="false">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<ListBox ItemsSource="{Binding ItemList}">
<ListBox.ItemTemplate>
<DataTemplate >
<StackPanel Style="{StaticResource hzp}">
<Label />
<Button Click="Button_Click" Content="TestButton"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Code-behind:
Imports System.Collections.ObjectModel
Class MainWindow
Public TestList As New ObservableCollection(Of TestClass)
Private Sub MainWindow_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
DgTest.ItemsSource = TestList
TestList.Add(New TestClass(0))
TestList.Add(New TestClass(1))
End Sub
Public Class TestClass
Private _ItemList As New List(Of String)
Private _id As Integer
Public Property ItemList() As List(Of String)
Get
Return _ItemList
End Get
Set(ByVal value As List(Of String))
_ItemList = value
End Set
End Property
Public Property Id() As Integer
Get
Return _id
End Get
Set(ByVal value As Integer)
_id = value
End Set
End Property
Public Sub New(ByVal id As Integer)
_ItemList.Add("String1")
_id = id
End Sub
End Class
Private Sub Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
End Sub
End Class
And in App Resources:
<Style TargetType="StackPanel" x:Key="hzp">
<Setter Property="Orientation" Value="Horizontal"/>
<Setter Property="Background" Value="Orange"/>
</Style>
Now here's the strange thing. If the Stackpanel Style is removed, the button will work. If the Click event for the button is removed, it will load normally.
Seems like your event handler is gone from the code-behind file, check that first. Comment if that is not the case.
I believe I have found the answer to my own question. In the ListBox bound to an ObservableCollection all Styles must be DynamicResource. Using StaticResource worked well in 3.5 but not 4! Took a few hours of randomly testing everything to find this. Case closed