WPF Templates and binding to DataContext in a GridView - c#

I'm trying to create a series of bound columns in a RadGridView, and I'm using a template to create hyperlinks in two of the columns. Here is basically what I have:
<telerik:GridViewDataColumn IsReadOnly="True" UniqueName="Distributor" DataContext="{Binding Distributor}" CellTemplate="{StaticResource linkTemplate}"/>
and,
<DataTemplate x:Key="linkTemplate">
<TextBlock>
<Hyperlink DataContext={TemplateBinding DataContext} Click="Hyperlink_Click">
<TextBlock Text="{Binding Name}" />
</Hyperlink>
</TextBlock>
</DataTemplate>
The RadGridView itself is bound to a set of DistributorContainer objects that have, among other things, a Distributor property. The linkTemplate refers directly to properties in the Distributor object, so the hyperlink's datacontext needs to be set to the Distributor.
Unfortunately, the Hyperlink's data context is the DistributorContainer object. I'm using the linkTemplate (as well as the Hyperlink_Click handler) on lists that bind to lists of Distributors, and I'd really like to re-use this template since it's basically the same thing.
Why isn't the template getting the Distributor as its DataContext through the TemplateBinding to the GridViewDataColumn?

Here is an example how to achieve this:
XAML
<Grid>
<Grid.Resources>
<DataTemplate x:Key="linkTemplate">
<TextBlock>
<Hyperlink>
<TextBlock
Text="{Binding
Value.Name,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type telerik:GridViewCell}}}" />
</Hyperlink>
</TextBlock>
</DataTemplate>
</Grid.Resources>
<telerik:RadGridView ItemsSource="{Binding}" AutoGenerateColumns="False">
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn
DataMemberBinding="{Binding Distributor1}"
CellTemplate="{StaticResource linkTemplate}" />
<telerik:GridViewDataColumn
DataMemberBinding="{Binding Distributor2}"
CellTemplate="{StaticResource linkTemplate}" />
</telerik:RadGridView.Columns>
</telerik:RadGridView>
</Grid>
C#
namespace WpfApplication1
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext =
from i in Enumerable.Range(0, 10)
select new DistributorContainer()
{
ID = i,
Distributor1 = new Distributor() {
Name = String.Format("Distributor1 Name{0}", i) },
Distributor2 = new Distributor() {
Name = String.Format("Distributor2 Name{0}", i) }
};
}
}
public class DistributorContainer
{
public int ID { get; set; }
public Distributor Distributor1 { get; set; }
public Distributor Distributor2 { get; set; }
}
public class Distributor
{
public string Name { get; set; }
}
}

Related

Binding data from datagrid to ui components

I have a window with a datagrid getting his items from an ObservableCollection and I have some field on the side which I want to set from the selected row on the datagrid.
Problem is when I select a row the fields they are not set; how do they get set?
View
<Window x:Class="videotheque.View.FilmView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:videotheque.View"
xmlns:vm="clr-namespace:videotheque.ViewModel"
mc:Ignorable="d"
Title="Vos films" Height="480" Width="900">
<Window.DataContext>
<vm:FilmViewModel/>
</Window.DataContext>
<Grid>
<DataGrid SelectedItem="{Binding Path=SelectedMovie, Mode=TwoWay}" ItemsSource="{Binding ListFilm, Mode=TwoWay}" CanUserAddRows="False" AutoGenerateColumns="False" Width="499" HorizontalAlignment="Left" Margin="10,0,0,68" Height="371" VerticalAlignment="Bottom">
<DataGrid.Columns>
<DataGridTextColumn Header="Titre" Binding="{Binding Titre}" Width="150"/>
<DataGridTextColumn Header="Durée" Binding="{Binding Duree}"/>
<DataGridTextColumn Header="Age Min." Binding="{Binding AgeMinimum}"/>
<DataGridTextColumn Header="Langue" Binding="{Binding LangueMedia}" Width="50"/>
<DataGridTextColumn Header="Sous titres" Binding="{Binding SousTitres}" Width="60"/>
<DataGridTemplateColumn Header="Modifier">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button CommandParameter="{Binding Id}"
Command="{Binding Path=DataContext.ModifierLeMediaCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
Modifier
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Supprimer">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button CommandParameter="{Binding Id}"
Command="{Binding Path=DataContext.SupprimerLeMediaCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
Supprimer
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Label Content="Titre :" HorizontalAlignment="Left" Margin="520,11,0,0" VerticalAlignment="Top" Width="39"/>
<TextBlock Text="{Binding SelectedMovie.Titre, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Margin="568,16,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="0.362,-1.709"/>
<CheckBox IsChecked="{Binding SelectedMovie.Vu, Mode=TwoWay}" Content="Vu" HorizontalAlignment="Left" Margin="709,17,0,0" VerticalAlignment="Top"/>
<Label Content="Note :" HorizontalAlignment="Left" Margin="521,42,0,0" VerticalAlignment="Top"/>
<TextBlock HorizontalAlignment="Left" Margin="568,47,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top"/>
<Label Content="Synopsis :" HorizontalAlignment="Left" Margin="521,82,0,0" VerticalAlignment="Top"/>
<TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="525,113,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Height="95" Width="209"/>
<Label Content="Commentaire :" HorizontalAlignment="Left" Margin="519,223,0,0" VerticalAlignment="Top"/>
<TextBlock HorizontalAlignment="Left" Margin="525,254,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Height="128" Width="218"/>
<CheckBox IsChecked="{Binding SelectedMovie.SupportPhysique, Mode=TwoWay}" Content="Support physique" HorizontalAlignment="Left" Margin="709,56,0,0" VerticalAlignment="Top"/>
<CheckBox IsChecked="{Binding SelectedMovie.SupportNumerique, Mode=TwoWay}" Content="Support numérique" HorizontalAlignment="Left" Margin="709,88,0,0" VerticalAlignment="Top"/>
<Button Command="{Binding Path=DataContext.AjouterFilmCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
Content="Ajouter un film" HorizontalAlignment="Left" Margin="10,405,0,0" VerticalAlignment="Top" Width="182"/>
</Grid>
ViewModel (did not past all code, there is juste some command function for the buttons which works):
class FilmViewModel
{
public ObservableCollection<Media> ListFilm { get; set; }
private ICommand _modifierLeMediaCommand;
private ICommand _supprimerLeMedia;
private Media SelectedMovie;
public FilmViewModel()
{
this.ListFilm = new ObservableCollection<Media>();
this.LoadData();
}
public async void LoadData()
{
var context = await DataAccess.VideothequeDbContext.GetCurrent();
foreach (Media f in context.Medias.Where(m => m.TypeMedia.ToString() == "Film").ToList())
{
this.ListFilm.Add(f);
}
}
}
And the model :
public class Media
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime DateSortie { get; set; }
public TimeSpan Duree { get; set; }
public string Titre { get; set; }
public bool Vu { get; set; }
public int Note { get; set; }
public string Commentaire { get; set; }
public string Synopsis { get; set; }
public ETypeMedia.TypeMedia TypeMedia { get; set; }
public int AgeMinimum { get; set; }
public bool SupportPhysique { get; set; }
public bool SupportNumerique { get; set; }
public string Image { get; set; }
public ELangue.Langue LangueVO { get; set; }
public ELangue.Langue LangueMedia { get; set; }
public ELangue.Langue SousTitres { get; set; }
[InverseProperty(nameof(GenreMedia.Media))]
public List<GenreMedia> Genres { get; set; }
[InverseProperty(nameof(PersonneMedia.Media))]
public List<PersonneMedia> Personnes { get; set; }
[InverseProperty(nameof(Episode.Media))]
public List<Episode> Episodes { get; set; }
}
I did hear about INotifyPropertyChanged, but it looks like so long to do with what I have so I dont know...
Take your control which has a binding that says ="{Binding SelectedMovie.Vu, ...}".
Remember that Binding is just reflection into the instance object of what is defined which is to go to SelectedMovie.Vu.
The SelectedMovie property is set to private and reflection into that fails right there (issue #1).
So let us make SelectedMovie public. It still fails! Even when the selection changes the binding does not get the current data. Why?
The why is that when the control is placed on the screen it attempts a binding at that time which the property is Null. So it does not show anything.
When the property changes, the binding is still failing because it is not receiving an event for a change so it can replace the null value.
Why?
The property does not follow INotifyPropertyChanged so no event announces that a change has been made because the control's binding will listen to a change event for SelectedMovie, but SelectedMovie never announces a change.
But there is a possible third problem even after the SelectedMovie reports a change by using INotifyPropertyChanged. Because the binding is looking for a child actual change notification from .Vu.
The .Vu does not announce a change because it also doesn't do InotifyPropertyChanged. Even if it did...it doesn't announce a change when SelectedMovie is set. How would it know?
You can make your binding system work, but you are going to have to do some extra notify events when the top level object is set. What you should use is a binding to the named DataGrid itself. Here is an example using a similar ListBox:
Example
In our Window or Page or Control I am going to setup some data in the XAML for our example.
Data
<Window.Resources>
<model:Orders x:Key="Orders">
<model:Order CustomerName="Alpha"
OrderId="997"
InProgress="True" />
<model:Order CustomerName="Beta"
OrderId="998"
InProgress="False" />
<model:Order CustomerName="Omega"
OrderId="999"
InProgress="True" />
<model:Order CustomerName="Zeta"
OrderId="1000"
InProgress="False" />
</model:Orders>
</Window.Resources>
ListBox
Then I am going to host/bind the data in a ListBox in which every line will be a TextBox to show the CustomerName.
<ListBox ItemsSource="{StaticResource Orders}" x:Name="lbOrders">
<ListBox.Resources>
<DataTemplate DataType="{x:Type model:Order}">
<TextBlock Text="{Binding Path=CustomerName}" />
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=InProgress}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
So we have a listbox that shows the data:
Show Selected Item's Order Number
So now we add another control under it which will show the user's selected item's order number. We will bind by using the ElementName which will point to our listbox control's SelectedItem dependancy property with the name of the control we have given which is lbOrders. On that property it will hold the selected instance and we will drill down to OrderId.
<Label Content="{Binding SelectedItem.OrderId, ElementName=lbOrders}"/>
So when we select Omega we get "999" shown.

Property setter in binding called twice

In order to try to be generic I implemented that WPF and C# Binding in the view but the problem is that the setter of the Value property in the Item class is called two times successively (without passing in any other function - I checked the call stack)
I don't really know if it's a problem of binding or a problem in the code but If you have any idea I would like to hear your feedback.
<DataGrid Grid.Row="1" ItemsSource="{Binding Questions}" AutoGenerateColumns="False" SelectedItem="{Binding Path=DataContext.Answer.QuestionItem, RelativeSource={RelativeSource AncestorType=UserControl}}" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="Questions" Width="SizeTocells" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Question}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Answers" Width="SizeToCells" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" >
<ListView ItemsSource="{Binding Path=DataContext.Answer.Answers, Mode=OneWay, RelativeSource={RelativeSource AncestorType=UserControl}}">
<ListView.ItemTemplate>
<DataTemplate>
<RadioButton GroupName="{Binding Path=GroupName, Mode=OneWay}" Content="{Binding Path=Content, Mode=OneWay}" IsChecked="{Binding Path=Value, Mode=TwoWay}" Margin="0, 0, 10, 0" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
public class Item : BindableBase
{
public string Content { get; }
public string GroupName { get; }
private bool _val;
public bool Value
{
get { return _val; }
set
{
SetProperty(ref _val, value);
}
}
public Item(string content, string groupName, bool value = false)
{
Content = content;
GroupName = groupName;
Value = value;
}
}
public class AnswerModel
{
public List<Item> Answers { get; }
public AnswerModel(List<string> possibleAnswers)
{
Answers = new List<Item>();
for (int i = 0; i < possibleAnswers.Count(); ++i)
{
char c = Convert.ToChar('A' + i);
var letter = Convert.ToString(c);
Answers.Add(new Item(letter, "group" + letter));
}
}
}
public class InsertWordQuestionViewModel : BindableBase, INavigationAware
{
public AnswerModel Answer { get; private set; }
public void OnNavigatedTo(NavigationContext navigationContext)
{
PossibleAnswers = new List<string>() { "A test", "B test2", "C test3"};
Questions = navigationContext.Parameters["questions"] as List<QuestionModel<string, string>>;
Answer = new AnswerModel(PossibleAnswers);
}
}
Value property in the Item class is called two times successively - I guess it happens in different items: one item/RadioButton gets unchecked, and another item/RadioButton gets checked. so value argument should be different for those two consequtive invocations

Binding not working in WPF Datagrid second row header

My Xaml-
<Grid>
<DataGrid Name="DataGrid" AutoGenerateColumns="False">
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="19" />
<TextBlock Text="{Binding Path=Items.Year, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=DataGridRowHeader}}" Foreground="#9493CF" FontSize="16" />
</StackPanel>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" />
<DataGridTextColumn Header="Course" Binding="{Binding Path=Course}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
in C#
var itemList = new List<Items>();
itemList.Add(new Items { Year = 22, Course = "B.SC", Name = "Jegadees" });
itemList.Add(new Items { Year = 23, Course = "M.SC", Name = "Arun" });
itemList.Add(new Items { Year = 55, Course = "B.Tech", Name = "Kanaga" });
DataGrid.ItemsSource = itemList;
Item Collection -
public class Items
{
public string Name { get; set; }
public string Course { get; set; }
public int Year { get; set; }
}
The ancestor with a DataContext of Item is the DataGridRow of the corosponding DataGridRowHeader
<TextBlock Text="{Binding Path=DataContext.Year,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=DataGridRow}}" />
With this piece of code RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGridRowHeader}}", you are travelling upto Visual Tree till you find first DataGridRowHeader.
Issue is in your Path - Path=Items.Year.
It will search for property Items in DataGridRowHeader which obviously it doesn't have.
You need to change Path to like this DataContext.Year because DataContext will get you actual object it's binded to i.e. an instance of Items class and then bind to its property Year in that instance.
So correct binding would be like this:
<TextBlock Text="{Binding Path=DataContext.Year,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=DataGridRowHeader}/>

Accessing ListViewItems Within a Custom INotifyPropertyChanged Class

I have a ListView with a bound ItemsSource and a GridViewColumns to display a given items property. Here is a snippet of the important bits:
<ListView Name="listView1" ItemsSource="{Binding Path=listItemCollection}" DataContext="{Binding ListItem}">
<ListView.View>
<GridView x:Name="gridViewMain">
<GridViewColumn x:Name="listViewItemNumberColumn" Width="50">
<GridViewColumnHeader>#</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="idTextBlock" KeyboardNavigation.IsTabStop="False" HorizontalAlignment="Center" Text="{Binding Path=Id}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn x:Name="listViewItemNameColumn" Width="500">
<GridViewColumnHeader>Name</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="nameTextBox" TextChanged="nameTextBox_TextChanged" Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
...
I want to be able to focus on a specific field/textbox within a user-selected ListViewItem. I am able to find the ListViewItem from a given index using this code:
listView1.ItemContainerGenerator.ContainerFromIndex(i);
All is well and good so far. That is until I want to actually access the content within the ListViewItem. Instead of ListViewItem.Content returning a group of controls which I am expecting, it returns my custom class ListItem. Here is a snippet of what that class looks like:
public class ListItem : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
public TextBlock idTextBlock;
public GridView gridViewMain;
public bool Selected { get; set; }
public string ItemName { get; set; }
I am completely lost in how I would for example focus onto the nameTextBox text box control within a ListViewItem.
Is it possible to somehow link the controls such as the nameTextBox and idTextBlock to my custom ListItem class?
There is a property in ListView SelectedItem. You need to bind this to.
In your view
<ListView Name="listView1" ItemsSource="{Binding Path=listItemCollection}" SelectedItem="{Binding SelectedListItem}" DataContext="{Binding ListItem}">
And in your class where you have listItemCollection, add a SelectedListItem variable.
public ListViewItem SelectedListViewItem {get; set;}
EDIT:
Remove ListView. Use DataGridOnly.
XAML:
<DataGrid ItemsSource="{Binding MyItems}" SelectedItem="{Binding SelectedItem}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="175" />
<DataGridTextColumn Binding="{Binding Path}" Header="Path" Width="75" />
</DataGrid.Columns>
</DataGrid>
ViewModel:
ObservableCollection<MyItem> MyItems {get; set;}
MyItem SelectedItem {get; set;}
public class MyItem : INotifyPropertyChanged
{
public string Name {get; set;}
public string Path {get; set;}
}

WPF datagridtextcolumn - always show textbox

By default the WPF datagridtext appears as a label and enters an edit state upon clicking. Is there a way to modify the column so that the textbox is always visible (instead of depending on the click event)?
Thanks in advance,
JP
I updated my answer based on your clarification in your comment. You can set the template yourself for cells. Below is a sample where the age column uses textblocks.
XAML:
<Window x:Class="GridTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
Height="300" Width="300">
<StackPanel>
<Controls:DataGrid Name="dataGrid" AutoGenerateColumns="False" >
<Controls:DataGrid.Columns>
<Controls:DataGridTextColumn
Header="Name"
Binding="{Binding Path=Name}" />
<Controls:DataGridTemplateColumn Header="Age">
<Controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Age}" />
</DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
<Controls:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Age}" />
</DataTemplate>
</Controls:DataGridTemplateColumn.CellEditingTemplate>
</Controls:DataGridTemplateColumn>
</Controls:DataGrid.Columns>
</Controls:DataGrid>
</StackPanel>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.Windows;
namespace GridTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
dataGrid.ItemsSource = new List<Person>(
new Person[]
{
new Person("Bob", 30),
new Person("Sally", 24),
new Person("Joe", 17)
});
}
}
public class Person
{
public String Name { get; set; }
public int Age { get; set; }
public Person(String name, int age)
{
Name = name;
Age = age;
}
}
}

Categories