Generic DataTemplate used in multiple GridViewColumns - c#

I have a GridView that displays some values:
<ListView ItemsSource="{Binding MyDataSource}">
<ListView.View>
<GridView>
<GridViewColumn Header="Date1" DisplayMemberBinding="{Binding Date1}" />
<GridViewColumn Header="Date2" DisplayMemberBinding="{Binding Date2}" />
...other Columns, not necessarily containing dates...
</GridView>
</ListView.View>
</ListView>
This works fine. Now I want to create a data template that formats a date in a specific way:
<DataTemplate x:Key="MySpecialDate">
<TextBlock Text="{Binding StringFormat={}{0:yyyy.MM.dd}}" />
</DataTemplate>
Adding CellTemplate won't work as long as DisplayMemberBinding is specified. Thus, I have to remove the DisplayMemberBinding attribute:
<GridViewColumn Header="Date1" CellTemplate="{StaticResource MySpecialDate}" />
<GridViewColumn Header="Date2" CellTemplate="{StaticResource MySpecialDate}" />
Here's the question: Now that DisplayMemberBinding is gone, how do I tell the GridView which property to display? GridViewColumn does not have a DataContext property.
Of course, I could put the name of the property (Date1 resp. Date2) into the DataTemplate, but then I would need one template for each property and that would defeat the whole purpose of having a template.
<!-- I don't want that -->
<DataTemplate x:Key="MySpecialDate1">
<TextBlock Text="{Binding Date1, StringFormat={}{0:yyyy.MM.dd}}" />
</DataTemplate>
<DataTemplate x:Key="MySpecialDate2">
<TextBlock Text="{Binding Date2, StringFormat={}{0:yyyy.MM.dd}}" />
</DataTemplate>

Related to your question is mine: Pass multiple resources into a DataTemplate which I finally found a solution for.
I think you cannot get around the definition of a template for every such date. But depending on the complexity of your display template this solution keeps the additional DataTemplates code to a minimum:
<DataTemplate x:Key="DateX">
<ContentPresenter ContentTemplate="{StaticResource MySpecialDate}" local:YourClass.AttachedDate="DateX"/>
</DataTemplate>
These additional DataTemplates use an attached property which can then be used by a converter in the DataTemplate "MySpecialDate".
The attached property has to be defined in the code behind. The answer to my own question contains a complete example.

If the format is really that widely used, then you could use the following
In DateTimeColumn.cs
public class DateTimeColumn : GridViewColumn
{
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (Equals(e.PropertyName, "DisplayMemberBinding") && DisplayMemberBinding != null)
{
DisplayMemberBinding.StringFormat = "{0:yyyy.MM.dd}";
}
}
}
XAML code...
<local:DateTimeColumn DisplayMemberBinding="{Binding Date1}" />
(I don't like the format as a magic string, but the code can be modified as needed.)

Related

WPF How to display relational data in DataGrid's TextBlock like in ComboBox

When searching for help on this topic, I typically find information on how to populate a ComboBox with related data (display name in one table for the foreign key in originating table), but I am already able to do that. I am looking for a solution on how to achieve the same result in a TextBlock.
In my project I have created a link to an SQL database using EntityFramework.
There are two tables: Personnel and Role.
They are related with Personnel.RoleIdFk and Role.RoleId.
I am able to get a combobox to display the Role.RoleName for the associated RoleIdFk.
In my ViewModel:
private ICollection<Role> roleList;
public ICollection<Role> RoleList
{
get { return roleList; }
set { roleList = value; NotifyPropertyChanged("RoleList"); }
}
private Role selectedRole;
public Role SelectedRole
{
get { return selectedRole; }
set { selectedRole = value; NotifyPropertyChanged("SelectedRole"); }
}
public void SetSelectedRole()
{
if (SelectedPerson == null)
{
SelectedPerson = PersonnelList.Select(p => p)
.FirstOrDefault();
}
SelectedRole = RoleList.Where(p => p.RoleId == SelectedPerson.RoleIdFk)
.FirstOrDefault();
}
And the XAML for the ComboBox:
<ComboBox x:Name="cboRole"
Grid.Column="1" Grid.Row="0"
Width="150" Height="35"
ItemsSource="{Binding RoleList}"
DisplayMemberPath="RoleName"
SelectedItem="{Binding SelectedRole}"
/>
My problem is I am also displaying a DataGrid for the Personnel table.
In this DataGrid, I would like to have the associated RoleName displayed instead of the numerical foreign key.
The current XAML for the DataGrid:
<Window.DataContext>
<viewModel:MainWindowVM />
</Window.DataContext>
<ListView Name="lstPersonnel"
Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1"
ItemContainerStyle="{StaticResource ItemContStyle}"
ItemsSource="{Binding PersonnelList}" >
<ListView.View>
<GridView>
<GridViewColumn Width="75" Header="First Name" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="75" Header="Last Name" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding LastName}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="75" Header="Role" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding RoleIdFk}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
This is what the grid looks like:
For the final column, I have tried altering the text field in various ways.
I have also tried using other Templates and Styles. None of these have been successful.
Is there a way to actually perform this in XAML itself, or am I going to have to set up some sort of value converter to accomplish this?
NOTE: I had thought of using some sort of "switch/case" scenario. While it would work here, I need to figure out something that can be more dynamic. Later in the project I will need to associate the PersonnelId with other items. A "switch/case" scenario is not feasible with 300+ people.
"Role" should be available in "Personnel" object. So you can simply use "Role.RoleName" instead of "RoleIdFk":
<TextBlock Text="{Binding Role.RoleName}" />
In cases when you don't have a referenced object, it's better to join two collections and use a result as an ItemsSource of a DataGrid. Here is an example:
Joining two tables in one datagrid usig Linq C# WPF
There is also a solution with IMultiValueConverter, but it seems overcomplicated:
Joining 2 collections into datagrid using multibinding

How do you use a CellTemplateSelector with databindings on a GridView?

I'm trying to bind to properties on my GridView's DataContext and use a DataTemplateSelector to assign the correct template to the cell, but I can't seem to find the correct way to do this. Binding to DisplayMemberBinding overrides the template selector, but setting the CellTemplateSelector property binds to the DataContext rather than the properties I want to select templates for.
This answer seems to describe exactly what I'm looking for, but I'm having trouble finding information on how to implement what it describes: https://stackoverflow.com/a/12519433/1756960 .
This is what I tried using that isn't working (simplified for posting):
<ListView ItemsSource="{Binding Items}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
DisplayMemberBinding="{Binding Name}"
CellTemplateSelector="{StaticResource ContentTypeTemplateSelector}" />
<GridViewColumn Header="Data"
DisplayMemberBinding="{Binding}"
CellTemplateSelector="{StaticResource ContentTypeTemplateSelector}" />
</GridView>
</ListView.View>
</ListView>
The first thing I would recommend doing is to differentiate your Content Template selectors in one of two ways. The First is to simply have more than one template selector class. the second is to have two instances, whose templates are assign different bindings.
<Resources>
<ns:TemplateSelector x:Key="NameTemplateSelector">
<ns:TemplateSelector.Template1>
<DataTemplate>
<!-- Something bound to Name -->
</DataTemplate>
</ns:TemplateSelector.Template1>
</ns:TemplateSelector>
<ns:TemplateSelector x:Key="DataTemplateSelector">
<ns:TemplateSelector.Template1>
<DataTemplate>
<!-- Something bound to Data -->
</DataTemplate>
</ns:TemplateSelector.Template1>
</ns:TemplateSelector>
The reference to Attached Properties (see MSDN) would have you make a property, attach it to the template selector, and then access that data from the TemplateSelector's code.

Listview DataTemplate Binding returning an error

I'm trying to switch over to WPF from Winform and so far it's a pain.
Anyway, I am trying to get this binding thing to work using DataTemplate.
I have a class:
public class TranslatorListItem
{
public string Item { get; set; }
public string OriginalMessage { get; set; }
public string TranslatedMessage { get; set; }
public string Sector { get; set; }
}
Items are added like this:
TranslatorListItem TLI = new TranslatorListItem();
TranslatorLVI.Items.Add(TLI);
My XAML DataTemplate:
<DataTemplate x:Key="MyDataTemplate">
<Border BorderBrush="#FFA4D5E5" BorderThickness="1,1,0,0" Margin="6">
<StackPanel Margin="6,2,6,2">
<TextBox Text="{Binding}" TextWrapping="Wrap" BorderThickness="0" BorderBrush="#00000000" />
</StackPanel>
</Border>
</DataTemplate>
This is how I'm trying to bind the data but it's returning this error: "Two-way binding requires Path or XPath."
<ListView Margin="23,224,27,54" Name="TranslatorLVI" ItemsSource="{Binding}" HorizontalContentAlignment="Stretch"
ItemContainerStyle="{StaticResource MyItemContainerStyle}">
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header="Item" DisplayMemberBinding="{Binding Path=Item}" CellTemplate="{StaticResource MyDataTemplate}" />
<GridViewColumn Header="Original Message" Width="300" CellTemplate="{StaticResource MyDataTemplate}" />
<GridViewColumn Header="Translated Message" DisplayMemberBinding="{Binding Path=TranslatedMessage}" CellTemplate="{StaticResource MyDataTemplate}" />
<GridViewColumn Header="Sector" DisplayMemberBinding="{Binding Path=Sector}" />
</GridView>
</ListView.View>
</ListView>
I need TranslatedMessage binding to be editable. So that field wont be Read-Only.
I heard I may need to set Two-way binding, but I am not sure how to do so.
Any help is appreciated.
Thanks!
I'm trying to switch over to WPF from Winform and so far it's a pain.
No, it's not. WPF is the best UI Framework in the history of mankind. winforms (or anything else) can't even be compared with it.
Your problem is you're trying to use a winforms approach in WPF and you fail.
WPF does not support developers with a winforms mentality.
All the horrible code behind hacks you're used to from winforms are completely unneeded in WPF.
Please read Rachel's Excellent Answer (and linked blog posts) about the mindshift needed when upgrading from winforms to WPF.
As mentioned in #JasRaj's answer, you're missing a DisplayMemberBinding in this case. I tested your code and added that, like this:
<GridViewColumn Header="Original Message"
DisplayMemberBinding="{Binding OriginalMessage}"
Width="300"/>
And it worked perfectly.
After a second read to your question I realized you wanted to make one of these columns editable.
First of all I will say that while what you want can be achieved with a ListView, using a DataGrid is easier.
You need to remove the DisplayMemberBindings from the ListView in order to use a CellTemplate, and therefore you need to modify the template slightly:
<DataTemplate x:Key="MyDataTemplate">
<Border BorderBrush="#FFA4D5E5" BorderThickness="1,1,0,0" Margin="6">
<TextBox Text="{Binding TranslatedMessage}"
TextWrapping="Wrap"
BorderThickness="0"
BorderBrush="#00000000" />
</Border>
</DataTemplate>
I have placed a ListView and a DataGrid side by side so you can compare them:
Here is the XAML:
<UniformGrid Columns="2">
<ListView ItemsSource="{Binding}" HorizontalContentAlignment="Stretch">
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header="Item" DisplayMemberBinding="{Binding Path=Item}"/>
<GridViewColumn Header="Original Message" DisplayMemberBinding="{Binding OriginalMessage}" Width="300"/>
<GridViewColumn Header="Translated Message" CellTemplate="{StaticResource MyDataTemplate}" />
<GridViewColumn Header="Sector"/>
</GridView>
</ListView.View>
</ListView>
<DataGrid ItemsSource="{Binding}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Item" Binding="{Binding Path=Item}" IsReadOnly="True"/>
<DataGridTextColumn Header="Original Message" Binding="{Binding OriginalMessage}" IsReadOnly="True" Width="300"/>
<DataGridTextColumn Header="Translated Message" Binding="{Binding TranslatedMessage}" />
<DataGridTextColumn Header="Sector" Binding="{Binding Sector}"/>
</DataGrid.Columns>
</DataGrid>
</UniformGrid>
Notice how the ListView requires DataTemplates in order to support edition, and is read-only by default, while the DataGrid is editable by default, does not need any special templats and requires that you add the IsReadOnly="True" to columns that are intended to be read-only.
Also, I noticed you're adding items manually to the ListView using procedural code. This is not desired in WPF. You must use an ObservableCollection<> and manipulate that collection, and let the WPF Binding engine update the UI for you.
It is recommended and desired that you Separate logic/data from UI in WPF.
If you need further clarification please let me know.
WPF Rocks.
Bind the second columns of Gridview with OriginalMessage property.
like :-
DisplayMemberBinding="{Binding Path=OriginalMessage}"
it should work.
In my opinion you dont fully use the datatemplate. you just use it to display one property;
In fact you can use a DataTemplate to display all data in thie class (TranslatorListItem);
such as this
<DataTemplate x:Key="MyDataTemplate">
<Border BorderBrush="#FFA4D5E5" BorderThickness="1,1,0,0" Margin="6">
<TextBox Text="{Binding Item}"
TextWrapping="Wrap"
BorderThickness="0"
BorderBrush="#00000000" />
<TextBox Text="{Binding TranslatedMessage}"
TextWrapping="Wrap"
BorderThickness="0"
BorderBrush="#00000000" />
( ..... follow up)
</Border>
</DataTemplate>
So your ListView can design like this:
<ListView ItemsSource="{Binding}" HorizontalContentAlignment="Stretch"
ItemTemplate="{StaticResource MyDataTemplate}">
</ListView>

A table which has binding ability and also manual editing

What I want is to have a table having a first column with every row named by me one by one, not binding to some property. But all of the second column should be binded to a property. I cannot do these two with gridview since it only makes binding and does not let me to write something manually. On the other hand, I havent seen any flowdocument table tutorial in which someone mention binding. So what is the proper object suitable to fulfill mentioned two things?
ListView and GridView should meet your requirements. You have the ability to set bindings on the GridViewColumns by using DisplayMemberPath or in the CellTemplate. You can also choose not to use bindings in a column.
Take this for example:
<ListView ItemsSource="{Binding NameValuePairs}">
<ListView.Resources>
<DataTemplate x:Key="FirstCellTemplate">
<Grid Width="100">
<TextBox Width="75"/>
</Grid>
</DataTemplate>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn CellTemplate="{StaticResource FirstCellTemplate}"/>
<GridViewColumn DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn DisplayMemberBinding="{Binding Value}"/>
</GridView>
</ListView.View>
</ListView>
NameValuePairs is an ObservableCollection<NameValuePair> and NameValuePair is a view model with a Name and Value properties. The first column is contains a TextBox on each row, the second is a TextBlock with the Name, and the third is a TextBlock with the Value.

Adding an Image to ListView in WPF like this ...?

I'm gonna create a ListView in WPF like the below image
(source: picfront.org)
http://www.picfront.org/d/7xuv
I mean I wanna add an image beside of Gravatar label within Name column.
Would it be OK if you guided me ?
Edit:
The image is output of a method. The method makes the image from a base-64 string.
As long as you're already familiar with how to data bind a ListView then it's pretty simple really. In your cell template you would just need a StackPanel with an Image and a TextBlock side by side.
<ListView>
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Source="{Binding IconUri}"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn ... />
<GridViewColumn ... />
</GridView>
</ListView.View>
</ListView>

Categories