UserControl for different data-objects - c#

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.

Related

Binding Combobox inside DataGrid

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.

Add items to wpf DataGrid in design mode

I have a kind of complex type (with nested class)
namespace Restore
{
public class Item
{
public string Title { get; set; }
public ImageSource Image { get; set; }
}
public class ItemSummary
{
public ImageSource Status { get; set; }
public Item Item { get; set; }
public string Description { get; set; }
}
}
And my xaml
<Window x:Class="Restore.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Restore"
<Window.Resources>
<BitmapImage x:Key="Error" UriSource="../../Images/Error.png" />
</Window.Resources>
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Status" Width="45" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Status}" Width="16" Height="16" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Object" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Item.Image}" Width="16" Height="16" />
<TextBlock Text="{Binding Item.Title}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Description" Width="*" Binding="{Binding Description}" />
</DataGrid.Columns>
<local:ItemSummary Item="{???}" Status="{StaticResource Error}" Description="1 item" />
<local:ItemSummary Item="{???}" Status="{StaticResource Success}" Description="2 item" />
<local:ItemSummary Item="{???}" Status="{StaticResource Warning}" Description="3 item" />
</DataGrid>
</Window>
I don't know how to set item value to Item="{???}"

How to create a DatagridTemplateColumn with checkbox and binding to a datasouce?

Apologies in advance if I'm overlooking something - I'm still finding my feet working with Xaml rather than windows forms.
I'm trying to bind a data source to a DataGrid where one of the columns is a checkbox. My original solution worked fine for this, but required the user to double click the checkbox:
<Window x:Class="ExecBoxInvoices.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="507" Width="676">
<Grid Margin="0,0,0,51">
<DataGrid x:Name="InvoiceDG" HorizontalAlignment="Left" Height="165" Margin="134,251,0,0" VerticalAlignment="Top" Width="400" ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Generate" Binding="{Binding Generate}" Width="80"/>
<DataGridTextColumn Header="Table_Number" Binding="{Binding Table_Number}" Width="120"/>
<DataGridTextColumn Header="Transaction_Date" Binding="{Binding Transaction_Date}" Width="175"/>
<DataGridTextColumn Header="Transaction_ID" Visibility="Hidden" Binding="{Binding Transaction_ID}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
The new solution for single click checkboxes is displayed below, but doesn't work (error is shown on the Binding="{Binding Generate}"):
<Window x:Class="ExecBoxInvoices.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="507" Width="676">
<Grid Margin="0,0,0,51">
<DataGrid x:Name="InvoiceDG" HorizontalAlignment="Left" Height="165" Margin="134,251,0,0" VerticalAlignment="Top" Width="400" ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Generate" Width="60" Binding="{Binding Generate}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Table_Number" Binding="{Binding Table_Number}" Width="120"/>
<DataGridTextColumn Header="Transaction_Date" Binding="{Binding Transaction_Date}" Width="175"/>
<DataGridTextColumn Header="Transaction_ID" Visibility="Hidden" Binding="{Binding Transaction_ID}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
For reference, the code used to set the source is:
InvoiceDG.ItemsSource = recordCollection;
Where recordCollection is a list of:
class InvoiceRow
{
public bool Generate { get; set; }
public string Table_Number { get; set; }
public string Transaction_Date { get; set; }
public string Transaction_ID { get; set; }
public InvoiceRow(bool generate, string table_Number, string transaction_Date, string transaction_ID)
{
this.Generate = generate;
this.Table_Number = table_Number;
this.Transaction_Date = transaction_Date;
this.Transaction_ID = transaction_ID;
}
}
Try this:
<DataGridTemplateColumn Header="Generate" Width="60">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Generate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

WPF DataGridCell Style binding issue

I'm facing a strange issue binding Collection to DataGrid on which when selecting a row the binding seems to disappear (I have an empty cell)
Here is my object list
public ReadOnlyCollection<ItemPoint> CurrentItemPoints { get; private set; }
ItemPoint object is defined as :
public sealed class ItemPoint : PropertyChangedNotifierBase
{
private bool _IsSubscribed = false;
private string _Name = string.Empty;
private string _Value = string.Empty;
private DateTime _ValueTime = DateTime.UtcNow;
public bool IsSubscribed
{
get { return _IsSubscribed; }
set
{
if (_IsSubscribed != value)
ChangeSubscription(value);
ChangeProperty("IsSubscribed", ref _IsSubscribed, value);
}
}
public string Name
{
get { return _Name; }
private set
{
ChangeProperty("Name", ref _Name, value);
}
}
public string Value
{
get { return _Value; }
set
{
_Value = value;
}
}
public DateTime ValueTime
{
get { return _ValueTime; }
private set
{
ChangeProperty("ValueTime", ref _ValueTime, value);
}
}
}
here is my xaml:
<DataGrid Name="PointsList" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="2,0,0,0"
ItemsSource="{Binding CurrentItemPoints, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding Path=IsSubscribed, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Width="10">
<DataGridCheckBoxColumn.CellStyle>
<Style>
<EventSetter Event="CheckBox.Checked" Handler="SubscriptionCheckBox_Checked"/>
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
<DataGridTextColumn Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" Header="Name" Width="*"/>
<DataGridTextColumn Binding="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, IsAsync=True, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}" Header="WorkingValueColumn" Width="*"/>
<DataGridTextColumn Binding="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, IsAsync=True, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}" Header="NonWorkingValueColumn" Width="*">
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Path=ValueTime, UpdateSourceTrigger=PropertyChanged, NotifyOnTargetUpdated=True, Mode=OneWay, StringFormat='{}{0:dd-MM-yyyy HH:mm:ss.fff}'}" Header="Time" Width="*">
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
If I now display the datagrid everything is ok... until I select any cell of the grid.
Doing that I will have the "WorkingValueColumn" working well... meaning that will keep displaying the selected item value while the "NonWorkingValueColumn" will display an empty cell...
Here is a screenshot of part of datagrid as sample:
I've been looking for a while now without finding any kind of explication... I though I could have missed some selection template or something like that but I really don't know.
Thanks for your help!
I finally could workaround that strange behavior changing the DataTextColumn by a DataTemplateColumn as that:
<DataGridTemplateColumn Header="Value">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=OneWay, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}" HorizontalAlignment="Stretch"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}" BorderThickness="0" HorizontalAlignment="Stretch"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

Overriding a control's name for screen reader purposes

I have a WPF DataGrid control that is binding to a collection of objects. Everything is rendering on-screen as it should be.
ToString() has been overridden due to requirements elsewhere in the application.
The issue is that, when read by a screen reader (such as Microsoft's built-in Narrator), or when inspected via a tool like AccChecker / Inspect, the control's name is the overridden ToString value.
I want to be able to specify a descriptive name for the screen reader, but I can't find a method of doing so. I've tried setting AutomationProperties.Name, AutomationProperties.ItemType, and so forth, but none of the properties in AutomationProperties seem to have the desired effect.
Optimally, I'd be able to do this for both the data item itself, and also for the individual members of the columns.
Here's a complete demo of the issue I'm experiencing:
<DataGrid x:Name="dgTest" ItemsSource="{Binding}" AutoGenerateColumns="false" AutomationProperties.Name="Test">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name" IsReadOnly="True" Width="2*" AutomationProperties.Name="Test2">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="ID" IsReadOnly="True" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Id}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
And the code:
public class FooItem
{
public Guid Id { get; set; }
public string Name { get; set; }
public override string ToString()
{
return string.Concat(Id.ToString(), " : ", Name);
}
}
public partial class MainWindow : Window
{
public readonly List<FooItem> fooList = new List<FooItem>();
public MainWindow()
{
fooList.Add(new FooItem { Id = Guid.NewGuid(), Name = "Test 1" });
fooList.Add(new FooItem { Id = Guid.NewGuid(), Name = "Test 2" });
fooList.Add(new FooItem { Id = Guid.NewGuid(), Name = "Test 3" });
InitializeComponent();
dgTest.DataContext = fooList;
}
}
And just for completeness, here's a screenshot of Inspector.
full size image
I found a solution... I had to use a style on DataGrid.ItemContainerStyle and DataGridTemplateColumn.HeaderStyle to set AutomationProperties.Name.
Ex:
<DataGrid x:Name="dgTest" ItemsSource="{Binding}" AutoGenerateColumns="false" AutomationProperties.Name="Test">
<DataGrid.ItemContainerStyle>
<Style>
<Setter Property="AutomationProperties.Name" Value="Row Item" />
</Style>
</DataGrid.ItemContainerStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name" IsReadOnly="True" Width="2*" AutomationProperties.Name="Test2">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="ID" IsReadOnly="True" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Id}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

Categories