I have a DataGrid with 3 column inside: Name -DataGridTextColumn, Value-DataGridTextColumn, ParamJson - DataGridTemplateColumn with combobox and has a ICollectionView of ParameterGrid as a source.
There is a ViewModel that provides ICollectionView of ParameterGrid. ParameterGrid contains 4 parameters: Name, Value, ParamJson, SelectedParamJson.
I need to handle all rows of DataGrid, so i use the command that get all dataGrid as a Command Parameter and than iterate over DataGrid.Items (I could use directly ICollectionView?).
The question is How to bind properly SelectedItem in Combobox in DataGrid to SelectedParamJson?
Xaml:
<DataGrid Grid.Row="0"
x:Name="parametersGridView"
AutoGenerateColumns="False"
VerticalScrollBarVisibility="Visible"
CanUserAddRows="False"
ItemsSource="{Binding PropertiesGrid}"
Margin="5"
AlternatingRowBackground="LightGoldenrodYellow">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"
Binding="{Binding Path=Name}"
IsReadOnly="True" />
<DataGridTextColumn Header="Value"
Binding="{Binding Path=Value}" />
<DataGridTemplateColumn Header="Parameter" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox MinWidth="100"
MaxWidth="150"
ItemsSource="{Binding Path=ParamsJson}"
SelectedItem="{Binding Path=SelectedParamJson, RelativeSource={RelativeSource AncestorType=DataGrid}}"
StaysOpenOnEdit="True"
IsEditable="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid > <DataGrid Grid.Row="0"
x:Name="parametersGridView"
AutoGenerateColumns="False"
VerticalScrollBarVisibility="Visible"
CanUserAddRows="False"
ItemsSource="{Binding PropertiesGrid}"
Margin="5"
AlternatingRowBackground="LightGoldenrodYellow">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"
Binding="{Binding Path=Name}"
IsReadOnly="True" />
<DataGridTextColumn Header="Value"
Binding="{Binding Path=Value}" />
<DataGridTemplateColumn Header="Parameter" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=ParamsJson}"
SelectedItem="{Binding Path=SelectedParamJson, RelativeSource={RelativeSource AncestorType=DataGrid}}"
StaysOpenOnEdit="True"
IsEditable="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid >`
ViewModel
class DataGridViewModel : DependencyObject
{
public DataGridViewModel()
{
var data = new DataEmulator();
PropertiesGrid = CollectionViewSource.GetDefaultView(data.GetCommonParameters());
}
public ICollectionView PropertiesGrid
{
get { return (ICollectionView)GetValue(PropertiesGridProperty); }
set { SetValue(PropertiesGridProperty, value); }
}
public static readonly DependencyProperty PropertiesGridProperty =
DependencyProperty.Register("PropertiesGrid", typeof(ICollectionView), typeof(DataGridViewModel), new PropertyMetadata(null));
public ICommand TestCommand
{
get
{
return new RelayCommand
{
ExecuteAction = a =>
{
// here SelectedParamJson should be as Selected in combobox,
// but it is always null because of the wrong binding
},
CanExecutePredicate = p =>
{
return true;
}
};
}
}
}
public class ParameterGrid
{
public string Name { get; set; }
public string Value { get; set; }
public List<SamResult> ParamsJson { get; set; }
public SamResult SelectedParamJson { get; set; }
}
If you want to bind to the SelectedParamJson property that is defined in the same class as the ParamsJson property, you should not set the RelativeSource property to anything at all:
ItemsSource="{Binding Path=ParamsJson}"
SelectedItem="{Binding Path=SelectedParamJson}"
You should also move the ComboBox to the CellEditingTemplate of the DataGridTemplateColumn.
The thing is you have to define <DataGridTemplateColumn.CellEditingTemplate>, where you should do all bindings and <DataGridTemplateColumn.CellTemplate>, that present value when the cell is not active.
That seems to be strange that you could not bind everything inside <DataGridTemplateColumn.CellTemplate>, because you have to do an additional klick to activate combobox.
Xaml that works for me:
<DataGridTemplateColumn Header="Parameter" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=SelectedParamJson}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox MinWidth="100"
MaxWidth="150"
ItemsSource="{Binding Path=ParamsJson}"
SelectedItem="{Binding Path=SelectedParamJson}"
StaysOpenOnEdit="True"
IsEditable="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
your ancestor is not the datagrid its the datarow you want to get the datacontext from.
so you made a collection view of some source collection like list, this means every row in your Datagrid repesents a IJsonObject Object and there you should have your Collection for the ComboBox Column defined. Like a List that you can bind to the Column and in Turn you can have a IJsonProperty field that you can bind to the selected Item.
So then you should get a Combobox filled with yout List Items and if you select one of them the IJsonProerty Field will be set to the selected Item.
I hope its understandable since I dont really got a code snippet right now.
Related
Im my viewmodel I have a list of my custom objects of type IPC.Device bound to a property defined as
private ObservableCollection<IPC.Device> _devices;
public ObservableCollection<IPC.Device> Devices
{
get
{
return _devices;
}
set
{
_devices = value;
RaisePropertyChangedEvent ("Devices");
}
}
I use the ObservableCollection to populate a DataGrid, that I create with the following XAML (unnecessary parts are not shown)
<DataGrid x:Name="MainGrid" ItemsSource="{Binding Path=Devices}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False">
<DataGrid.Columns>
....
<DataGridTemplateColumn Header="{Binding Source={x:Static p:Resources.device_status},
Converter={StaticResource CapitalizeFirstLetterConverter}}"
Width="*" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=deviceStatus, Converter={StaticResource DeviceStatusToStringConverter}, Mode=OneWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
....
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Views:CameraLoginControl Visibility="{Binding Path=deviceStatus, Converter={StaticResource DeviceStatusUnauthorizedConverter}, Mode=OneWay}" />
<Views:TestSelectionControl Visibility="{Binding Path=deviceStatus, Converter={StaticResource DeviceStatusOnlineConverter}, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
When I update the ObservableCollection byt replacing an item with its updated copy I can see that the Datagrid is correctly updated, the row is updated and the DeviceStatusToStringConverter is fired. Unfortunately, the DeviceStatusUnauthorizedConverter and DeviceStatusOnlineConverter are never fired again, so the RowDetailsTemplate is still the one of the previous item.
What am I doing wrong?
Update
The IPC.Device is, stripped of unnecessary fields:
public class Device
{
[DataMember]
public DeviceStatus deviceStatus { get; set; }
...
}
You see [DataMember] beacsue I use this structure in many places, also for IPC. I have other Propertied marked [DataMember], thus I would exclude this as the reason why this happens. DeviceStatus is an enum.
There are several possibilities to do it.
You can implement INotifyPropertyChanged for the
Device.deviceStatus, then you don't need to replace an object in
ObservableCollection, just modify the property.
You can replace the whole ObservableCollection Devices = new
ObservableCollection...
You can remove and insert an element:
Devices.RemoveAt(i);
Devices.Insert(i, new Device());
I have a Template Column in my DataGrid that looks like this:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Item}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox
DisplayMemberPath="Item"
Header="Item"
ItemsSource="{Binding Data.AssetDescriptions, Source={StaticResource proxy}}"
SelectedValueBinding="{Binding AssetDescriptionID}"
SelectedValuePath="AssetDescriptionID" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
The ViewModel has a public property containing the Asset Descriptions:
public IEnumerable<AssetDescription> AssetDescriptions { get; set; }
Where AssetDescription is essentially:
public class AssetDescription
{
public int AssetDescriptionID { get; set; }
public string Item { get; set; } // Description
}
The DataGrid itself is bound to an ObservableCollection<Asset> Assets property, where Asset contains both AssetDescriptionID and Item (description). To accomplish that, I join the Assets table to the AssetDescriptions table, like so:
var assets = _conn.Query<Asset>(
#"SELECT A.AssetDescriptionID, D.Item
FROM Assets A
JOIN AssetDescriptions D
ON D.AssetDescriptionID = A.AssetDescriptionID");
Assets = new ObservableCollection<Asset>(assets);
This all works perfectly, except that the TextBlock in the CellTemplate DataTemplate does not get updated to the new description when a new value is selected in the ComboBox.
How do I accomplish that?
The problem is that you only bind to the AssetDescriptionID-Property of your Asset.
Item will never be touched (which your CellTemplate binds to).
Option 1:
Try using a DataGridComboBoxColumn instead of DataGridTemplateColumn
Item (on Asset) is then no longer needed
<DataGridComboBoxColumn
DisplayMemberPath="Item"
Header="Item"
ItemsSource="{Binding Data.AssetDescriptions, Source={StaticResource proxy}}"
SelectedValueBinding="{Binding AssetDescriptionID}"
SelectedValuePath="AssetDescriptionID">
</DataGridComboBoxColumn>
Option 2: If you really need the Item/description on your Asset
Easiest solution will be to bind the whole object(AssetDescription).
Change your Asset to this
class Asset
{
...
public AssetDescription AssetDescription {get;set;}
...
}
And your CellEditingTemplate to this
<DataTemplate>
<ComboBox
DisplayMemberPath="Item"
ItemsSource="{Binding Data.AssetDescriptions, Source={StaticResource proxy}}"
SelectedItem="{Binding AssetDescription }" />
</DataTemplate>
And CellTemplate to this
<TextBlock Text="{Binding AssetDescription.Item}" />
Edit:
You can also use a DataGridComboBoxColumn for Option 2
<DataGridComboBoxColumn
DisplayMemberPath="Item"
Header="Item"
ItemsSource="{Binding Data.AssetDescriptions, Source={StaticResource proxy}}"
SelectedItemBinding="{Binding AssetDescription}">
</DataGridComboBoxColumn>
I wanna validate my DataGrid with an RowValidationRule, but the ValidationRule will not be called if I select another item within the combobox.
The Validation Rule will only be called if I change the text within the DatagridTextColumn.
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding SelectedFiles, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding SelectedDataGridRow, UpdateSourceTrigger=PropertyChanged}"
CellEditEnding="SelectedFilesDataGrid_CellEditEnding">
<DataGrid.RowValidationRules>
<valid:SaveComponentValidationRule ValidationStep="UpdatedValue"/>
</DataGrid.RowValidationRules>
<DataGrid.Resources>
<DataTemplate x:Key="comboTemplate">
<ComboBox IsSynchronizedWithCurrentItem="False"
ItemsSource="{Binding DataContext.FileDataTypes, ValidatesOnDataErrors=True,
RelativeSource={RelativeSource AncestorType=local:SaveComponentView},UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding RelatedFileType, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding IsEditing, RelativeSource={RelativeSource AncestorType=DataGridCell}}"/>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="File Name" Binding="{Binding Path=FileName, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridTemplateColumn Header="Data Type"
CellTemplate="{StaticResource comboTemplate}"
CellEditingTemplate="{StaticResource comboTemplate}" />
</DataGrid.Columns>
</DataGrid>
The binding SelectedFiles of the datagrid is an ObservableCollection, which contains the objects of the posted class below.
public class SelectedFileModel
{
public FileType RelatedFileType {get; set;}
public string FileName {get; set;}
public string FilePath {get; set;}
}
The combobox contains a enum of the type FileType.
And the CellEditEnding method in my View.
private void SelectedFilesDataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
e.EditingElement.IsEnabled = false;
}
I have a ComboBox column inside a DataGrid. I need it to display values based on another WPF control, not in DataGrid. So, the values of ComboBox should change according to that control. I have created an ObservableCollection and binded it to the ComboBox but it does not display any values. For displaying dynamically, I have added DropOpenOpened event. But the ComboBox does not display any value. The list that populates the ComboBox is getting updated but its not displaying anything.
Below is the xaml code.
The DataGrid is bound to another List, whose values I fetch from DB.
<DataGrid x:Name="grid1" AutoGenerateColumns="False" Grid.Row="1" Grid.RowSpan="2" Grid.Column="1" Grid.ColumnSpan="2"
AlternatingRowBackground="Azure" AlternationCount="2" CanUserReorderColumns="True" CanUserResizeRows="True" CanUserSortColumns="True"
DataContext="attr">
<DataGrid.Columns>
<DataGridTextColumn Width="*" Binding="{Binding modify_user}" Header="Modified By" IsReadOnly="True"></DataGridTextColumn>
<DataGridTextColumn Width="*" Binding="{Binding modify_date}" Header="Modified Date" IsReadOnly="True"></DataGridTextColumn>
<DataGridTemplateColumn Header="Source" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding source_value}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox x:Name="combo_source" ItemsSource="{Binding Path=sourceComboDropdown}"
DisplayMemberPath="desc" SelectedValuePath="id"
SelectedItem="{Binding source_value}"
SelectedValue="{Binding Path=source_value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
DropDownOpened="combo_source_DropDownOpened"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Target value" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding target_value}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding targetComboDropdown}"
DisplayMemberPath="desc" SelectedValuePath="id"
SelectedItem="{Binding target_value}"
SelectedValue="{Binding Path=target_value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
DropDownOpened="ComboBox_DropDownOpened"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The class of observablecollection is:
public static ObservableCollection<Source> sourceComboDropdown =
new ObservableCollection<Source>();
public static ObservableCollection<Source> targetComboDropdown =
new ObservableCollection<Source>();
public class Source
{
public string id { get; set; }
public string desc { get; set; }
}
Where desc is the DisplayMember value and id is SelectedValue.
The class for DataGrid list is:
public static ObservableCollection<Attribute_Param> attr =
new ObservableCollection<Attribute_Param>();
public class Attribute_Param
{
public string source_value { get; set; }
public string target_value { get; set; }
public string modify_user { get; set; }
public DateTime modify_date { get; set; }
}
I have tried adding static resource. But since I need to dynamically update the values, I couldn't figure out a work around to use it.
I think I am missing something really small but cant figure out what.
I think it is unable to find the list sourceComboDropdown since the dataContext of the DataGrid is attr which does not CONTAIN sourceCombo drop down.
I would set my datagrid datacontext to the whole viewmodel. i.e DataGrid.DataContext=model in the constructor of the code behind.
Then I can bind the datagrid to attr using : ItemsSource="{Binding attr}" inside the DataGrid tag . Assuming attr is immediately in the VieWModel model. That way it should be able to detect the combo box items soruce which is now in the data context i.e model contains sourceDropDown .
If that doesnt work , try this :
<DataGridTemplateColumn >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate >
<ComboBox Loaded="LoadItemsSource"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
private void LoadItemsSource(object sender, RoutedEventArgs e)
{
ComboBox comboBox = sender as ComboBox;
comboBox.ItemsSource=model.sourceDropDown;
}
Let's say I have the following two classes in my model-folder:
public class Simple
{
public int Id { get; set; }
public string Display { get; set; }
public double Value1 { get; set; }
}
and
public class Extended : Simple
{
public double Value2 { get; set; }
public string Name { get; set; }
}
To display a collection of Simple I've created a UserControl which looks like:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding}"
CanUserAddRows="False" CanUserDeleteRows="False">
<DataGrid.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="4,2"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Id" Width="Auto" SortMemberPath="Id">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Display" Width="*" SortMemberPath="Display">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Display}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Value1" Width="*" SortMemberPath="Value1">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value1}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The usage of this UserControl looks like:
<local:SEControl DataContext="{Binding Simples}"/>
Now I want to display a collection of Extended-Objects. My approach would now be to write another UserControl which just have two columns more than the other one.
My question now is: Is there a way to just write one UserControl which can handle Simple and Extended?
I also thought about a DataTemplate, but there I have to duplicate the logic too.
You can try to use Visibility.
Add the extended columns as here :
<DataGridTemplateColumn Visibility="{Binding IsExtended, Converter={StaticResource BoolToVis}, FallbackValue=Hidden}" Header="Test" Width="*" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Test}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
and using a converter you can switch whenever you need to show or not these columns.
So, when you change the ItemSource in order to contain Extended items, change also the IsExtended property to true and vice-versa.