I'm relatively new to WPF and am having a hard time binding a custom class to a datagrid. While the text properties are OK (they are read-only anyway), the togglebuttons for the boolean properties are not updated in my item list, and they are also not displayed according to the values set initially. They do however respond correctly to clicks in the UI.
<Style x:Key="ToggleImageStyleBien" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<Image Name="img" Source="Images/transp.png"/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter TargetName="img" Property="Source" Value="Images/good.png"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's the DataGrid itself:
<DataGrid AutoGenerateColumns="False" Height="Auto" Name="dataGridRevision" Width="Auto" Margin="6,6,6,0" ItemsSource="{Binding}" VerticalScrollBarVisibility="Auto" VerticalGridLinesBrush="{x:Null}">
<DataGrid.Columns>
<DataGridTextColumn Header="Code" Binding="{Binding Code}" Visibility="Collapsed"/>
<DataGridTextColumn Header="DescripciĆ³n" Binding="{Binding Description}" IsReadOnly="True"/>
<DataGridTemplateColumn Header="Bien">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ToggleButton Style="{StaticResource ToggleImageStyleBien}" Click="ToggleButton_Click" IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=ReviewItem.Good, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Comentario" />
</DataGrid.Columns>
</DataGrid>
This is the class I'm binding to:
public class ReviewItem
{
public string Code { set; get; }
public string Description { set; get; }
public bool Good { set; get; }
public bool Bad { set; get; }
public string Comment { set; get; }
}
As far as I can tell, I'm not using the right Binding property in the ToggleButton, but I have tried a lot and have run out of ideas. The list properties are not changing on click, the values are not displayed according to the data.
Please help...
Thanks!
Joerg.
Changed the class to this, based on other examples found:
public class ReviewItem : INotifyPropertyChanged
{
public string Code { set; get; }
public string Description { set; get; }
public bool Bad { set; get; }
public string Comment { set; get; }
private bool _isChecked;
public bool Good
{
get { return _isChecked; }
set
{
System.Diagnostics.Debug.WriteLine("Good = " + value);
_isChecked = value;
OnPropertyChanged("Good");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
You are currently binding to UserControl.ReviewItem, which is not a valid property on a UserControl.
Try binding to DataContext.ReviewItem instead which will bind to UserControl.DataContext.ReviewItem
If you do not mean to bind to UserControl.DataContext.ReviewItem.Good, and instead want to bind to DataGrid.ReviewItems[x].Good, then you only need to bind IsChecked="{Binding Good}". This is because the default DataContext on a DataGridCell is the row's data item, so if your collection contains a list of ReviewItems, the DataContext of the cell is already set to a ReviewItem
Also, I would highly recommend using Snoop for debugging WPF bindings. You can use it on your application to see what the DataContext is for individual UI Elements, and figure out if your bindings are correct or not.
Related
Totally i am new to WPF, I need to solve the problem. Could anybody give me a sample xaml code without using code-behind.
Based on the questType (Check,radio, combo) Control need to be created based on the ObserverableCollection.
public class Order
{
public int OrderCode {get;set;}
public string Description {get;set;}
public ObserverableCollection<question> Questions{get;set;}
}
public class question
{
public string questType {get;set;}
public string Question {get;set;}
public ObserverableCollection<Answer> Answers {get;set;}
}
public class Answer
{
public string Ans{get; set;}
}
Based on the questType (Check,radio, combo)
Control need to be created based on the ObserverableCollection.
example:
1001 Pencil Gender? oMale oFemale oOther
[]Checkbox1 []Checkbox2
1002 Pen Fasting? oYes oNo
Here is how I would do it:
Code behind:
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
namespace Ans
{
public class Order
{
public int OrderCode { get; set; }
public string Description { get; set; }
public ObservableCollection<Question> Questions { get; set; }
}
public class Question
{
public string questType { get; set; }
public string Label { get; set; }
public ObservableCollection<Answer> Answers { get; set; }
}
public class Answer
{
public string Ans { get; set; }
public bool IsSelected { get; set; }
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
#region Order
/// <summary>
/// Order Dependency Property
/// </summary>
public static readonly DependencyProperty OrderProperty =
DependencyProperty.Register("Order", typeof(Order), typeof(MainWindow),
new FrameworkPropertyMetadata((Order)null));
/// <summary>
/// Gets or sets the Order property. This dependency property
/// indicates ....
/// </summary>
public Order Order
{
get { return (Order)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
#endregion
public MainWindow()
{
InitializeComponent();
Order = new Order()
{
Questions = new ObservableCollection<Question>()
{
new Question()
{
questType = "Combo",
Label = "Combo",
Answers = new ObservableCollection<Answer>()
{
new Answer(){Ans = "Female"},
new Answer(){Ans = "Male"}
}
},
new Question()
{
questType = "Check",
Label = "Multi",
Answers = new ObservableCollection<Answer>()
{
new Answer(){Ans = "Female"},
new Answer(){Ans = "Male"}
}
},
new Question()
{
questType = "Radio",
Label = "Radio",
Answers = new ObservableCollection<Answer>()
{
new Answer(){Ans = "Female"},
new Answer(){Ans = "Male"}
}
}
}
};
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
foreach(Question q in Order.Questions)
{
Console.WriteLine( q.Label + " : " + string.Join(", " , q.Answers.Where(a=>a.IsSelected).Select(a=>a.Ans)) );
}
}
}
}
XAML:
<Window x:Class="Ans.MainWindow"
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:Ans"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Key="ComboQuestion">
<ComboBox ItemsSource="{Binding Answers}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Ans}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="CheckQuestion">
<ItemsControl ItemsSource="{Binding Answers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Ans}" IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
<DataTemplate x:Key="RadioQuestion">
<ItemsControl ItemsSource="{Binding Answers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Ans}" IsChecked="{Binding IsSelected, Mode=TwoWay}" GroupName="{Binding DataContext.Label, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Order.Questions}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Label}"/>
<ContentControl x:Name="ccQuestion" Grid.Column="1" Content="{Binding}" Margin="10"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding questType}" Value="Combo">
<Setter TargetName="ccQuestion" Property="ContentTemplate" Value="{StaticResource ComboQuestion}"/>
</DataTrigger>
<DataTrigger Binding="{Binding questType}" Value="Check">
<Setter TargetName="ccQuestion" Property="ContentTemplate" Value="{StaticResource CheckQuestion}"/>
</DataTrigger>
<DataTrigger Binding="{Binding questType}" Value="Radio">
<Setter TargetName="ccQuestion" Property="ContentTemplate" Value="{StaticResource RadioQuestion}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Order" Click="Button_Click" VerticalAlignment="Bottom"/>
</Grid>
</Window>
The only thing I've added to your model is IsSelected property that allows to know if this answer was selected.
The other important thing is the Radios. Their GroupName property defines the scope. So if no GroupName is set then when clicking on a radio in one question it will unselect radio in another question. I used Question label in my solution however it only works if labels are unique.
Another point is that data triggers are OK if you have 3-5 question types and if thay are only based on the questionType. However for more complex scenarios you can look for ItemTemplateSelector. It allows to write C# code that will select the template based on each item in ItemsControl.
I need to present a WPF GridView where one column is a Combobox, The user can select one value from the list or enter a new value so I set the IsComboBoxEditable to true but the problem is that if the user types a value that is not in the ItemsSource the Text is blank when the Combobox looses the focus.
Note : I don't want, when a new value is typed , this value to be
added to the ItemsSource. I only need to save it's string value in row
that bounded to it.
I also need DropDownOpened event, to populate it's ItemsSource.
Here is my code:
<telerik:GridViewDataColumn Header="Description">
<telerik:GridViewDataColumn.CellTemplate>
<DataTemplate>
<telerik:RadComboBox IsEditable="True" ItemsSource="{Binding Descriptions}" Text="{Binding Description1,Mode=TwoWay}" DropDownOpened="descriptionRadComboBox_DropDownOpened"/>
</DataTemplate>
</telerik:GridViewDataColumn.CellTemplate>
</telerik:GridViewDataColumn>
Description1 is string property, and Descriptions is List of string that populate in runtime.(When DropDownOpened Event occurred)
Like you mentioned, your goal is, simply, to "Editable ComboBox".
(And, of course, you don't want to add new Item to ItemsSource)
<telerik:GridViewDataColumn UniqueName="description1" Header="Description">
<telerik:GridViewDataColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description1}"></TextBlock>
</DataTemplate>
</telerik:GridViewDataColumn.CellTemplate>
<telerik:GridViewDataColumn.CellEditTemplate>
<DataTemplate>
<telerik:RadComboBox Name="SLStandardDescriptionsRadComboBox" IsEditable="True"
ItemsSource="{Binding DataContext.SLStandardDescriptions, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
DisplayMemberPath="SLStandardDescriptionTitle" DropDownOpened="Description_DropDownOpened">
</telerik:RadComboBox>
</DataTemplate>
</telerik:GridViewDataColumn.CellEditTemplate>
</telerik:GridViewDataColumn>
Codebehinde :
private void RadGridView_CellEditEnded(object sender, GridViewCellEditEndedEventArgs e)
{
if (e.Cell.Column.UniqueName == "description1")
{
RadComboBox combo = e.Cell.ChildrenOfType<RadComboBox>().FirstOrDefault();
if (combo != null)
{
List<Description> comboItems = combo.ItemsSource as List<Description>;
string textEntered = e.Cell.ChildrenOfType<RadComboBox>().First().Text;
bool result = comboItems.Contains(comboItems.Where(x => x.DescriptionTitle == textEntered).FirstOrDefault());
if (!result)
{
comboItems.Add(new Description { DescriptionTitle = textEntered });
combo.SelectedItem = new Description { DescriptionTitle = textEntered };
}
if (_viewModel.AccDocumentItem != null)
{
if (e.Cell.Column.UniqueName == "description1")
_viewModel.AccDocumentItem.Description1 = textEntered;
}
}
}
}
Here is the solution for .net DataGrid control:
<DataGrid ItemsSource="{Binding Path=Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Title}" ></DataGridTextColumn>
<DataGridComboBoxColumn SelectedValueBinding="{Binding ComboItem.ID}" DisplayMemberPath="ComboTitle" SelectedValuePath="ID">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.ComboItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.ComboItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
<Setter Property="IsEditable" Value="True" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
Surely you can do this using Telerik DataGrid control as well.
And here is my ViewModel:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
ComboItems = new ObservableCollection<ComboItem>()
{
new ComboItem(){ID=1,ComboTitle="ComboItem1"},
new ComboItem(){ID=2,ComboTitle="ComboItem2"},
new ComboItem(){ID=3,ComboTitle="ComboItem3"}
};
Items = new ObservableCollection<Item>()
{
new Item(){ID=1,Title="Item1",ComboItem=ComboItems[0]},
new Item(){ID=2,Title="Item2",ComboItem=ComboItems[1]},
new Item(){ID=3,Title="Item3",ComboItem=ComboItems[2]}
};
}
public ObservableCollection<Item> Items { get; set; }
public ObservableCollection<ComboItem> ComboItems { get; set; }
}
public class Item
{
public int ID { get; set; }
public string Title { get; set; }
public ComboItem ComboItem { get; set; }
}
public class ComboItem
{
public int ID { get; set; }
public string ComboTitle { get; set; }
}
I want to implement (file) Explorer like icon display. The items have date and label.
User should be able to edit the label:
Select an item
Click on label
Label's TextBlock is replaced with TextBox for editing
How to end editing (just for info):
Click anywhere outside of the TextBox
Press Enter keyboard key (by implementing ICommand?)
1st I tried to set the Visibility of TextBlock and TextBox in code found out it is not the 'right' way to to do. Maybe it is possible to edit item's Label using (Data)Triggers?
I can track the OnClickLabelBlock and set selectedMedia.IsEditing = true; but it does not fire the trigger.
Any idea why MediaItem.IsEditing property value change is notifying the DataTrigger? Is it something to do with the order of execution or priority mechanism?
I will pick the answer which guides me to the 'best' architecture to solve it.
Thanks.
XAML:
<Window x:Class="WPFComponents.DailyImages"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Model="clr-namespace:WPFComponents.Model"
Title="Media Items" Height="300" Width="300">
<ListView x:Name="_mediaItemList" ItemsSource="{Binding MediaItems}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionMode="Multiple">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate DataType="Model:MediaItem">
<Grid Width="80" Margin="4">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Image HorizontalAlignment="Center" Stretch="Uniform" Source="{Binding Path=IconPath}" Width="70" />
<StackPanel Grid.Row="2">
<TextBlock Text="{Binding Path=Date}" TextWrapping="Wrap" />
<TextBlock x:Name="_labelTextBlock" Text="{Binding Path=Label}" TextWrapping="Wrap"
PreviewMouseLeftButtonDown="OnClickLabelBlock">
</TextBlock>
<TextBox x:Name="_labelTextBox" Text="{Binding Path=Label}" Visibility="Collapsed"
TextWrapping="WrapWithOverflow" TextAlignment="Center">
</TextBox>
</StackPanel>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsEditing}" Value="True">
<Setter TargetName="_labelTextBlock" Property="Visibility" Value="Collapsed" />
<Setter TargetName="_labelTextBox" Property="Visibility" Value="Visible" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" VerticalAlignment="Top" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
Source:
public partial class DailyImages
{
public DailyImages()
{
InitializeComponent();
ViewModel.DailyImages dailyImages = new ViewModel.DailyImages();
// DailyImages has ObservableCollection<MediaItem> MediaItems property
_mediaItemList.DataContext = dailyImages;
}
private void OnClickLabelBlock(object sender, MouseButtonEventArgs e)
{
TextBlock notes = sender as TextBlock;
if (notes == null)
return;
MediaItem selectedMedia = notes.DataContext as MediaItem;
if (selectedMedia == null)
{
// TODO: Throw exception
return;
}
_mediaItemList.SelectedItems.Clear();
selectedMedia.IsSelected = true;
selectedMedia.IsEditing = true;
}
public class MediaItem
{
public MediaItem()
{
IsEditing = false;
IsSelected = false;
}
public DateTime Date { get; set; }
public string Label { get; set; }
public string IconPath { get; set; }
public bool IsEditing { get; set; }
public bool IsSelected { get; set; }
}
References:
Dependency Property Value Precedence
Part II: ListView & File Explorer Like Behaviour
MediaItem must implement INotifyPropertyChanged and each of its properties that must be bound, must call RaisePropertyChanged in order for the binding to work correctly. In your case, the Binding on IsEditing has no way to know that the value has changed.
To bind your IsEditing property, WPF has to be notified when it is modified.
Then you have to implement INotifyPropertyChanged in MediaItem. (Or add dependency properties)
public class MediaItem : INotifyPropertyChanged
{
public MediaItem()
{
IsEditing = false;
IsSelected = false;
}
// Use the same pattern for Date, Label & IconPath if these value may change after the MediaItem instance has been added to the collection MediaItems.
public DateTime Date { get; set; }
public string Label { get; set; }
public string IconPath { get; set; }
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
if (isSelected != value)
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
private bool isEditing;
public bool IsEditing
{
get { return isEditing; }
set
{
if (isEditing != value)
{
isEditing = value;
OnPropertyChanged("IsEditing");
}
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Otherwise, your code is correct.
I've looked at the answers to various questions, but haven't managed to map the content in the answers to the problem I'm attempting to solve. I've reduced it down to the following code (representative of the outcome I'm trying to achieve), and basically want to be able to render the Person.TitleId as its corresponding Title.TitleText when the row isn't being edited, and have the drop-down bound correctly so that it displays the TitleTexts in the drop-down and writes the associated TitleId back to the Person record when its changed.
In short, what do I put in my <DataGridComboBoxColumn> to achieve this?
App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var viewModel = new ViewModels.MainWindowViewModel();
var mainWindow = new MainWindow();
mainWindow.DataContext = viewModel;
mainWindow.ShowDialog();
}
MainWindow.xaml
<Grid>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Path=Contacts}">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Title" SelectedItemBinding="{Binding Person}">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Titles}"/>
<Setter Property="IsReadOnly" Value="True"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Titles}"/>
<Setter Property="DisplayMemberPath" Value="TitleText" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
Person.cs
public class Person
{
public int TitleId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
Title.cs
public struct Title
{
public Title(int titleId, string titleText)
: this()
{
TitleId = titleId;
TitleText = titleText;
}
public string TitleText { get; private set; }
public int TitleId { get; private set; }
public static List<Title> GetAvailableTitles()
{
var titles = new List<Title>();
titles.Add(new Title(1, "Mr"));
titles.Add(new Title(2, "Miss"));
titles.Add(new Title(3, "Mrs"));
return titles;
}
}
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModelBase
{
private ObservableCollection<Person> contacts;
private List<Title> titles;
public MainWindowViewModel()
{
titles = Title.GetAvailableTitles();
Contacts = new ObservableCollection<Person>();
Contacts.Add(new Person() { FirstName = "Jane", LastName = "Smith", TitleId = 2 });
}
public List<Title> Titles
{
get { return titles; }
}
public ObservableCollection<Person> Contacts
{
get { return contacts; }
set
{
if (contacts != value)
{
contacts = value;
this.OnPropertyChanged("Contacts");
}
}
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Here is a working code. The key point here was to use SelectedValueBinding instead of SelecteItemBinding.
<DataGridComboBoxColumn Header="Title"
SelectedValueBinding="{Binding TitleId}"
SelectedValuePath="TitleId"
DisplayMemberPath="TitleText"
>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Titles}"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Titles}"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
#SnowBear's answer worked well for me. But I want to clarify a detail of the binding.
In #Rob's example, both Title and Person classes use TitleID.
Therefore, in #SnowBear's answer, in the binding:
SelectedValueBinding="{Binding TitleId}"
it wasn't immediately obvious to me which class and property was being bound.
Because the SelectedValueBinding attribute appeared on the DataGridComboBoxColumn, it is binding to the ItemsSource of the containing DataGrid. In this case the Contacts collection of Person objects.
In my case, the DataGrid's DataSource collection was attributed with a property that was named different from the ValuePath of the ComboBox's ItemSource collection. So my SelectedValueBinding's value was bound to a different property than the property named in the ComboBox's SelectedValuePath.
I have a tabControl that is bound to an observable collection.
In the headerTemplate, I would like to bind to a string property, and in the contentTemplate I have placed a user-control.
Here's the code for the MainWindow.xaml:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="contentTemplate">
<local:UserControl1 />
</DataTemplate>
<DataTemplate x:Key="itemTemplate">
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</Grid.Resources>
<TabControl IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Pages}"
ItemTemplate="{StaticResource itemTemplate}"
ContentTemplate="{StaticResource contentTemplate}"/>
</Grid>
And its code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
public class MainWindowViewModel
{
public ObservableCollection<PageViewModel> Pages { get; set; }
public MainWindowViewModel()
{
this.Pages = new ObservableCollection<PageViewModel>();
this.Pages.Add(new PageViewModel("first"));
this.Pages.Add(new PageViewModel("second"));
}
}
public class PageViewModel
{
public string Name { get; set; }
public PageViewModel(string name)
{
this.Name = name;
}
}
So the problem in this scenario (having specified an itemTemplate as well as a controlTemplate) is that I only get one instance for the user-control, where I want to have an instance for each item that is bound to.
Try this:
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Pages}">
<TabControl.Resources>
<DataTemplate x:Key="contentTemplate" x:Shared="False">
<local:UserControl1/>
</DataTemplate>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="ContentTemplate" Value="{StaticResource contentTemplate}"/>
</Style>
</TabControl.Resources>
</TabControl>
Try setting
x:Shared="False"
When set to false, modifies Windows Presentation Foundation (WPF) resource retrieval behavior such that requests for a resource will create a new instance for each request, rather than sharing the same instance for all requests.
You need to override the Equals() Method of your PageViewModel class.
public override bool Equals(object obj)
{
if (!(obj is PageViewModel)) return false;
return (obj as PageViewModel).Name == this.Name;
}
Something like this should work.
Now it is looking for the same property of the value Name. Otherwise you could also add a ID Property which is unique.