Issue when Updating ItemsSource - wpf

I have a combobox that is editable by the user, so I bound the Text property to a property of my class. The ItemsSource of that same combobox is bound to an AsyncObservableCollection (which I did based on other posts and it works nicely).
However, I have a problem when updating the ItemsSource.
Here's the Steps to reproduce:
Select a value in the combobox drop down.
Type some text into the combobox. (say "aaa")
Update the ItemsSource. (via my button click)
Result: The MyText property remains set to the text you typed in ("aaa"), but the combo box shows a blank entry.
However, if you do the same steps above but skip Step 1, the combobox shows the text from the MyText property correctly. This leads me to believe that the selected index/selected value is being used to update the combobox after the update to the ItemsSource is done.
Any ideas on how I can keep the displayed value in sync with the MyText property after an update to the ItemsSource?
In the code provided below I'm updating the ItemsSource on the button click in order to reproduce.
Thank You!
XAML:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="comboBox1" VerticalAlignment="Top" Width="200" IsEditable="True"
DataContext="{Binding Path=MyDataClass}"
ItemsSource="{Binding Path=MyListOptions}"
SelectedIndex="{Binding Path=MySelectedIndex}"
Text="{Binding Path=MyText, UpdateSourceTrigger=LostFocus}"
>
</ComboBox>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="416,276,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
Code behind:
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Diagnostics;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public class DataClass : INotifyPropertyChanged
{
private string mytext = "";
public string MyText
{
get
{
return mytext;
}
set
{
mytext = value;
OnPropertyChanged("MyText");
}
}
private int myselectedindex = -1;
public int MySelectedIndex
{
get
{
return myselectedindex;
}
set
{
if (value != -1)
{
mytext = MyListOptions[value];
OnPropertyChanged("MyText");
}
}
}
private AsyncObservableCollection<string> mylistOptions = new AsyncObservableCollection<string>();
public AsyncObservableCollection<string> MyListOptions
{
get
{
return mylistOptions;
}
set
{
mylistOptions.Clear();
OnPropertyChanged("MyListOptions");
foreach (string opt in value)
{
mylistOptions.Add(opt);
}
OnPropertyChanged("MyListOptions");
}
}
public DataClass()
{
}
public event PropertyChangedEventHandler PropertyChanged;
internal void OnPropertyChanged(string prop)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
public DataClass MyDataClass { get; set; }
public MainWindow()
{
MyDataClass = new DataClass();
MyDataClass.MyListOptions.Add("Option 1 - Provides helpful stuff.");
MyDataClass.MyListOptions.Add("Option 2 - Provides more helpful stuff.");
MyDataClass.MyListOptions.Add("Option 3 - Provides extra helpful stuff.");
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
private void button1_Click(object sender, RoutedEventArgs e)
{
AsyncObservableCollection<string> newList = new AsyncObservableCollection<string>();
newList.Add("Option A - Provides helpful stuff.");
newList.Add("Option B - Provides more helpful stuff.");
newList.Add("Option C - Provides extra helpful stuff.");
MyDataClass.MyListOptions = newList;
}
}
}

Ok, I solved this problem by binding the SelectedValue to the same property as Text and setting its mode to OneWay.
<ComboBox Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="comboBox1" VerticalAlignment="Top" Width="200" IsEditable="True"
DataContext="{Binding Path=MyDataClass}"
ItemsSource="{Binding Path=MyListOptions}"
SelectedIndex="{Binding Path=MySelectedIndex}"
SelectedValue="{Binding Path=MyText, Mode=OneWay}"
Text="{Binding Path=MyText, UpdateSourceTrigger=LostFocus}"

Related

DataTemplate problems at runtime with TabControl

I'm trying to use a view-model-first approach and I've created a view-model for my customized chart control. Now, in my form, I want a TabControl that will display a list of XAML-defined charts defined as such:
<coll:ArrayList x:Key="ChartListTabs" x:Name="ChartList">
<VM:MyChartViewModel x:Name="ChartVM_Today" ChartType="Today" ShortName="Today"/>
<VM:MyChartViewModel x:Name="ChartVM_Week" ChartType="Week" ShortName="This Week"/>
<VM:MyChartViewModel x:Name="ChartVM_Month" ChartType="Month" ShortName="This Month"/>
<VM:MyChartViewModel x:Name="ChartVM_Qtr" ChartType="Quarter" ShortName="This Quarter"/>
<VM:MyChartViewModel x:Name="ChartVM_Year" ChartType="Year" ShortName="This Year"/>
<VM:MyChartViewModel x:Name="ChartVM_Cust" ChartType="Custom" ShortName="Custom"/>
</coll:ArrayList>
Trying to specify data templates for my tab headers and content, I have this:
<DataTemplate x:Key="tab_header">
<TextBlock Text="{Binding ShortName}" FontSize="16" />
</DataTemplate>
<DataTemplate x:Key="tab_content" DataType="{x:Type VM:MyChartViewModel}" >
<local:MyChartControl/>
</DataTemplate>
My TabControl is like this:
<TabControl ItemsSource="{StaticResource ChartListTabs}"
ItemTemplate="{StaticResource tab_header}"
ContentTemplate="{StaticResource tab_content}"
IsSynchronizedWithCurrentItem="True">
<!-- nothing here :) -->
</TabControl>
What happens is that the designer shows the tabs correctly and the first tab content (can't switch tabs because they are dynamically created) showing apparently the right view for the first chart, but when I run the application, all tabs show the same, default, uninitialized content (i.e. the same chart control without any properties set). Also, the instance seems to be the same, i.e. changing something on my custom control (e.g. a date box) this shows on all tabs.
It seems to me that the control (view) in the TabControl content stays the same (TabControl does this, as I've read elsewhere) and should only change DataContext when the tab changes, but it clearly doesn't.
Notes:
All my classes are DependencyObjects and my collections are ObservableCollections (with the exception of the ChartListTabs resource)
ShortName is the view-model property I want to have as tab header text
This question seems related but I can't connect the dots
Here is my solution used your code inside, please try to check this out.
Xaml
<Window x:Class="TabControTemplatingHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:tabControTemplatingHelpAttempt="clr-namespace:TabControTemplatingHelpAttempt"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<collections:ArrayList x:Key="ChartListTabs" x:Name="ChartList">
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Today" ChartType="Today" ShortName="Today"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Week" ChartType= "Week" ShortName="This Week"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Month" ChartType="Month" ShortName="This Month"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Qtr" ChartType= "Quarter" ShortName="This Quarter"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Year" ChartType= "Year" ShortName="This Year"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Cust" ChartType= "Custom" ShortName="Custom"/>
</collections:ArrayList>
<DataTemplate x:Key="TabHeader" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}">
<TextBlock Text="{Binding ShortName}" FontSize="16" />
</DataTemplate>
<DataTemplate x:Key="TabContent" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}" >
<tabControTemplatingHelpAttempt:MyChartControl Tag="{Binding ChartType}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{StaticResource ChartListTabs}"
ItemTemplate="{StaticResource TabHeader}"
ContentTemplate="{StaticResource TabContent}"
IsSynchronizedWithCurrentItem="True"/>
</Grid></Window>
Converter code
public class ChartType2BrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var key = (ChartType) value;
SolidColorBrush brush;
switch (key)
{
case ChartType.Today:
brush = Brushes.Tomato;
break;
case ChartType.Week:
brush = Brushes.GreenYellow;
break;
case ChartType.Month:
brush = Brushes.Firebrick;
break;
case ChartType.Quarter:
brush = Brushes.Goldenrod;
break;
case ChartType.Year:
brush = Brushes.Teal;
break;
case ChartType.Custom:
brush = Brushes.Blue;
break;
default:
throw new ArgumentOutOfRangeException();
}
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Main VM
public class MyChartViewModel:BaseObservableDependencyObject
{
private ChartType _chartType;
private string _shortName;
public ChartType ChartType
{
get { return _chartType; }
set
{
_chartType = value;
OnPropertyChanged();
}
}
public string ShortName
{
get { return _shortName; }
set
{
_shortName = value;
OnPropertyChanged();
}
}
}
public enum ChartType
{
Today,
Week,
Month,
Quarter,
Year,
Custom,
}
Inner user control XAML
<UserControl x:Class="TabControTemplatingHelpAttempt.MyChartControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tabControTemplatingHelpAttempt="clr-namespace:TabControTemplatingHelpAttempt">
<UserControl.Resources>
<tabControTemplatingHelpAttempt:ChartType2BrushConverter x:Key="ChartType2BrushConverterKey" />
<DataTemplate x:Key="UserContentTemplateKey" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Fill="{Binding ChartType, Converter={StaticResource ChartType2BrushConverterKey}}"/>
<TextBlock Grid.Row="0" Text="{Binding ShortName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Grid Grid.Row="1" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ChartType, UpdateSourceTrigger=PropertyChanged}">
<Grid.DataContext>
<tabControTemplatingHelpAttempt:TabContentDataContext/>
</Grid.DataContext>
<Rectangle VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Fill="{Binding BackgroundBrush}"/>
<TextBlock Text="{Binding Description, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl Content="{Binding }" ContentTemplate="{StaticResource UserContentTemplateKey}"/>
<!--<Grid.DataContext>
<tabControTemplatingHelpAttempt:TabContentDataContext/>
</Grid.DataContext>
<Rectangle VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Fill="{Binding BackgroundBrush}"/>
<TextBlock Text="{Binding Code, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>-->
</Grid>
Please keep in attention, that if you comment out the Grid.DataContext tag and comment in the ContentControl tag, your inner content won't be updated since it doesn't created depending on delivered MyChartViewModel. Elsewhere
I can't see any problems with your code.
Inner user control VM
public class TabContentDataContext:BaseObservableObject
{
private string _code;
private Brush _backgroundBrush;
public TabContentDataContext()
{
Init();
}
private void Init()
{
var code = GetCode();
Code = code.ToString();
BackgroundBrush = code%2 == 0 ? Brushes.Red : Brushes.Blue;
}
public virtual int GetCode()
{
return GetHashCode();
}
public string Code
{
get { return _code; }
set
{
_code = value;
OnPropertyChanged();
}
}
public Brush BackgroundBrush
{
get { return _backgroundBrush; }
set
{
_backgroundBrush = value;
OnPropertyChanged();
}
}
}
Observable object code
/// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
Update
Base Observable Dependency Object code
/// <summary>
/// dependency object that implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableDependencyObject : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
Regards.
While testing Ilan's answer I found out that when a DataContext is declared inside the control (i.e. as an instance of some class via a UserControl.DataContext tag), it imposes a specific object instance on the control and the ability to databind some other object to it is lost (probably because the WPF run-time uses SetData instead of SetCurrentData).
The recommended way to "test" your control in the designer is the d:DataContext declaration (which works only for the designer).

WPF - Dependency Property of Custom Control lost Binding at 2 way mode

I have this Custom Control
XAML:
<UserControl x:Class="WpfApplication1.UC"
...
x:Name="uc">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal">
<TextBox Text="{Binding Test, ElementName=uc}" Width="50" HorizontalAlignment="Left"/>
</StackPanel>
</UserControl>
C#
public partial class UC : UserControl
{
public static readonly DependencyProperty TestProperty;
public string Test
{
get
{
return (string)GetValue(TestProperty);
}
set
{
SetValue(TestProperty, value);
}
}
static UC()
{
TestProperty = DependencyProperty.Register("Test",typeof(string),
typeof(UC), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
public UC()
{
InitializeComponent();
}
}
And this is how i used that custom control:
<DockPanel>
<ItemsControl ItemsSource="{Binding Path=DataList}"
DockPanel.Dock="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" CommandParameter="{Binding}" Click="Button_Click"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<local:UC Test="{Binding SelectedString, Mode=OneWay}"/>
</DockPanel>
--
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
private ObservableCollection<string> _dataList;
public ObservableCollection<string> DataList
{
get { return _dataList; }
set
{
_dataList = value;
OnPropertyChanged("DataList");
}
}
private string _selectedString;
public string SelectedString
{
get { return _selectedString; }
set
{
_selectedString = value;
OnPropertyChanged("SelectedString");
}
}
public MainWindow()
{
InitializeComponent();
this.DataList = new ObservableCollection<string>();
this.DataList.Add("1111");
this.DataList.Add("2222");
this.DataList.Add("3333");
this.DataList.Add("4444");
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.SelectedString = (sender as Button).CommandParameter.ToString();
}
}
If I do not change text of UC, everything is ok. When I click each button in the left panel, button's content is displayed on UC.
But when I change text of UC (ex: to 9999), Test property lost binding. When I click each button in the left panel, text of UC is the same that was changed (9999). In debug I see that SelectedString is changed by each button click but UC's text is not.
I can 'fix' this problem by using this <TextBox Text="{Binding Test, ElementName=uc, Mode=OneWay}" Width="50" HorizontalAlignment="Left"/> in the UC.
But I just want to understand the problem, can someone help me to explain it please.
Setting the value of the target of a OneWay binding clears the binding. The binding <TextBox Text="{Binding Test, ElementName=uc}" is two way, and when the text changes it updates the Test property as well. But the Test property is the Target of a OneWay binding, and that binding is cleared.
Your 'fix' works because as a OneWay binding, it never updates Test and the binding is never cleared. Depending on what you want, you could also change the UC binding to <local:UC Test="{Binding SelectedString, Mode=TwoWay}"/> Two Way bindings are not cleared when the source or target is updated through another method.
The issue is with below line
<local:UC Test="{Binding SelectedString, Mode=OneWay}"/>
The mode is set as oneway for SelectString binding so text will be updated when the value from code base changes. To change either the source property or the target property to automatically update the binding source as TwoWay.
<local:UC Test="{Binding SelectedString, Mode=TwoWay}"/>

WPF TextBox Text Coercion

I have to implement WPF TextBox that will provide trimmed Text via binding.
At first glance this task looked fairly straightforward to me. I decided to use dependency property value coercion. Below I wrote my code, but this seems not to work. I get not trimmed strings in my bound properties. What am I doing wrong? Maybe I should take another approach?
public class MyTextBox : TextBox
{
static MyTextBox()
{
TextProperty.OverrideMetadata(typeof(MyTextBox), new FrameworkPropertyMetadata(string.Empty, null, new CoerceValueCallback(CoerceText)));
}
private static object CoerceText(DependencyObject d, object basevalue)
{
string s = basevalue as string;
if(s != null)
{
return s.Trim();
}
else
{
return string.Empty;
}
}
}
I added simple window to my app for testing.
Xaml:
<Window x:Class="TextBoxDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TextBoxDemo="clr-namespace:TextBoxDemo"
Title="MainWindow"
Width="525"
Height="350">
<Grid>
<TextBoxDemo:MyTextBox x:Name="textBox1"
Width="120"
Height="23"
Margin="55,73,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding Text}" />
<TextBoxDemo:MyTextBox x:Name="textBox2"
Width="120"
Height="23"
Margin="286,184,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding Text}" />
</Grid>
</Window>
And code-behind:
public partial class MainWindow : Window
{
private string _text;
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public string Text
{
get { return _text; }
set
{
_text = value;
MessageBox.Show(string.Format("|{0}|", _text));
}
}
}
Strangely enough, value coercion doesn't work well with binding.
This thread talks about the same issue and proposes one or two workarounds.
One of them is to call UpdateTarget() on the TextBox's binding expression explicitly:
textBox1.GetBindingExpression(MyTextBox.TextProperty).UpdateTarget();

Binding a ComboBox entirely in XAML

I have a ComboBox on a silverlight control, that I want to bind. Sounds simple, except what I'm finding is that because the data for the ItemsSource comes from a web service asynchronously, I need to use the code behind to bind the SelectedValue only after the data has come back.
The collection that the data goes in implements INotifyCollectionChanged and INotifyPropertyChanged, so it should all be working, and indeed the combo box loads properly, but there is no value pre-selected.
What I think is happening is that the SelectedValue is getting bound before the collection has loaded - when the combobox is empty - so nothing is selected, and then later when the data comes in, the combobox is populated, but it is not checking the selected value again.
So whilst I have this working if I use code behind to hook up events and creating bindings in code, I'd like to move this all to XAML with something like:
<ComboBox HorizontalAlignment="Stretch" Margin="5,3,9,127" Name="cboCategoryID" Grid.Row="4" Grid.Column="1"
ItemsSource="{StaticResource Categories}"
SelectedValue="{Binding CategoryID, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True}"
SelectedValuePath="CategoryID"
DisplayMemberPath="Caption"
VerticalAlignment="Center">
</ComboBox>
This correctly loads items, but doesn't bind the selected value. If I put the following code in the code-behind, it all works:
public MainControl()
{
InitializeComponent();
CategoryCollection cats = new CategoryCollection();
cats.Dispatcher = this.Dispatcher;
cats.LoadComplete += new EventHandler(cats_LoadComplete);
cboCategoryID.ItemsSource = cats;
cats.LoadAll();
}
private void cats_LoadComplete(object sender, EventArgs e)
{
cboCategoryID.SetBinding(ComboBox.SelectedValueProperty, new System.Windows.Data.Binding("CategoryID"));
}
Is there a way to do this without resorting to code behind?
Are you using mvvm? If so, you can try to set the ItemsSource and SelectedItem in the callback of the web service, or take a look at this post from Kyle.
http://blogs.msdn.com/b/kylemc/archive/2010/06/18/combobox-sample-for-ria-services.aspx
you are already using a collection that notifies of changes, so if the value that you are binding the SelectedValue to is notifying of changes, then all you have to do is set that property after the values are loaded from the webservice. it SHOULD update the combobox automatically, allowing you to do your binding purely in xaml.
public myObject CategoryID { get {....}
set {
this.categoryID = value;
RaisePropertyChanged("CategoryID");}
public void DataLoadedHandler()
{
CategoryID = 34; // this will cause the binding to update
}
take a look at this simple sample:
XAML:
<UserControl
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:sdk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" xmlns:local="clr-namespace:StackoverflowQuestions.Silverlight" xmlns:sdk1="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" x:Class="StackoverflowQuestions.Silverlight.MainPage"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<DataTemplate x:Key="Item">
<TextBlock Text="{Binding PropertyToBeWatched}" />
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<!--<sdk:DataGrid ItemsSource="{Binding MyList}" RowStyle="{StaticResource Style1}">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Binding="{Binding PropertyToBeWatched}" Header="Property1"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>-->
<ComboBox ItemsSource="{Binding MyList}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" Height="50" VerticalAlignment="Top" ItemTemplate="{StaticResource Item}" />
</Grid>
</UserControl>
Codebehind:
public partial class MainPage : UserControl, INotifyPropertyChanged
{
private ObservableCollection _myList;
private CustomClass _selectedItem;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<CustomClass> MyList
{
get { return _myList ?? (_myList = new ObservableCollection<CustomClass>()); }
set
{
_myList = value;
RaisePropertyChanged("MyList");
}
}
public CustomClass SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
protected void RaisePropertyChanged(string propertyname)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyname));
}
public MainPage()
{
InitializeComponent();
this.DataContext = this;
MyList.Add(new CustomClass() { PropertyToBeWatched = "1"});
MyList.Add(new CustomClass() { PropertyToBeWatched = "2" });
MyList.Add(new CustomClass() { PropertyToBeWatched = "2" });
MyList.Add(new CustomClass() { PropertyToBeWatched = "2" });
SelectedItem = MyList[1]; //Here is where it happens
}
}
By binding the SelectedItem of the ComboBox to an entity, we can achieve what you want. This works TwoWay ofcourse.
Hope this helps. :D

Binding WPF DataGrid to DataTable using TemplateColumns

I have tried everything and got nowhere so I'm hoping someone can give me the aha moment.
I simply cannot get the binding to pull the data in the datagrid successfully.
I have a DataTable that contains multiple columns with of MyDataType
public class MyData
{
string nameData {get;set;}
bool showData {get;set;}
}
MyDataType has 2 properties (A string, a boolean)
I have created a test DataTable
DataTable GetDummyData()
{
DataTable dt = new DataTable("Foo");
dt.Columns.Add(new DataColumn("AnotherColumn", typeof(MyData)));
dt.Rows.Add(new MyData("Row1C1", true));
dt.Rows.Add(new MyData("Row2C1", false));
dt.AcceptChanges();
return dt;
}
I have a WPF DataGrid which I want to show my DataTable.
But all I want to do is to change how each cell is rendered to show [TextBlock][Button] per cell with values bound to the MyData object and this is where I'm having a tonne of trouble.
My XAML looks like this
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="MyDataTemplate" DataType="MyData">
<StackPanel Orientation="Horizontal" >
<Button Background="Green" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,0,0" Content="{Binding Path=nameData}"></Button>
<TextBlock Background="Green" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5,0,0,0" Text="{Binding Path=nameData}"></TextBlock>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<dg:DataGrid Grid.Row="1" ItemsSource="{Binding}" AutoGenerateColumns="True"
x:Name="dataGrid1" SelectionMode="Single" CanUserAddRows="False"
CanUserSortColumns="true" CanUserDeleteRows="False" AlternatingRowBackground="AliceBlue"
AutoGeneratingColumn="dataGrid1_AutoGeneratingColumn" />
</Grid>
Now all I do once loaded is to attempt to bind the DataTable to the WPF DataGrid
dt = GetDummyData();
dataGrid1.ItemsSource = dt.DefaultView;
The TextBlock and Button show up, but they don't bind, which leaves them blank.
Could anyone let me know if they have any idea how to fix this.
This should be simple, thats what Microsoft leads us to believe.
I have set the Column.CellTemplate during the AutoGenerating event and still get no binding.
Please help!!!
Edit: Updated to reflect the input of Aran Mulholland (see comment)
Apparently the DataGrid is passing the entire DataRowView to each cell. That's why the binding doesn't work. Your DataTemplate expects the DataContext to be of type MyData, but instead it is of type DataRowView. My proposed (somewhat hack-ish) workaround to get the DataContext you want is to create a custom DataGridTemplateColumn that will extract the necessary item from the DataRowView. The code is below:
<Window x:Class="DataGridTemplateColumnSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="MyDataTemplate" DataType="DataRowView">
<StackPanel Orientation="Horizontal">
<Button Background="Green" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,0,0" Content="{Binding Path=nameData}"></Button>
<TextBlock Background="Green" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5,0,0,0" Text="{Binding Path=nameData}"></TextBlock>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<dg:DataGrid Grid.Row="1" AutoGenerateColumns="True" x:Name="dataGrid1" SelectionMode="Single"
CanUserAddRows="False" CanUserSortColumns="true" CanUserDeleteRows="False"
AlternatingRowBackground="AliceBlue" AutoGeneratingColumn="dataGrid1_AutoGeneratingColumn"
ItemsSource="{Binding}" VirtualizingStackPanel.VirtualizationMode="Standard" />
</Grid>
</Window>
using System.Data;
using System.Windows;
using Microsoft.Windows.Controls;
namespace DataGridTemplateColumnSample
{
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = GetDummyData().DefaultView;
}
private static DataTable GetDummyData()
{
var dt = new DataTable("Foo");
dt.Columns.Add(new DataColumn("OneColumn", typeof(MyData)));
dt.Columns.Add(new DataColumn("AnotherColumn", typeof(MyData)));
dt.Rows.Add(new MyData("Row1C1", true), new MyData("Row1C2", true));
dt.Rows.Add(new MyData("Row2C1", false), new MyData("Row2C2", true));
dt.AcceptChanges();
return dt;
}
private void dataGrid1_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var column = new DataRowColumn(e.PropertyName);
column.Header = e.Column.Header;
column.CellTemplate = (DataTemplate)Resources["MyDataTemplate"];
e.Column = column;
}
}
public class DataRowColumn : DataGridTemplateColumn
{
public DataRowColumn(string column) { ColumnName = column; }
public string ColumnName { get; private set; }
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var row = (DataRowView) dataItem;
var item = row[ColumnName];
cell.DataContext = item;
var element = base.GenerateElement(cell, item);
return element;
}
}
public class MyData
{
public MyData(string name, bool data) { nameData = name; showData = data; }
public string nameData { get; set; }
public bool showData { get; set; }
}
}
Note: This approach only appears to work with container virtualization off or in Standard mode. If the VirtualizationMode is set to Recycling the template is not applied.
After finding this thread and having trouble with the code shown here, I ran across this thread on MSDN, and it works much better! No virtualization problems at all so far as I've seen.
http://social.msdn.microsoft.com/Forums/en/wpf/thread/8b2e94b7-3c44-4642-8acc-851de5285062
Code:
private void dataGrid1_AutoGeneratingColumn(object sender, Microsoft.Windows.Controls.DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyType == typeof(MyData))
{
MyDataGridTemplateColumn col = new MyDataGridTemplateColumn();
col.ColumnName = e.PropertyName; // so it knows from which column to get MyData
col.CellTemplate = (DataTemplate)FindResource("MyDataTemplate");
e.Column = col;
e.Column.Header = e.PropertyName;
}
}
public class MyDataGridTemplateColumn : DataGridTemplateColumn
{
public string ColumnName
{
get;
set;
}
protected override System.Windows.FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
// The DataGridTemplateColumn uses ContentPresenter with your DataTemplate.
ContentPresenter cp = (ContentPresenter)base.GenerateElement(cell, dataItem);
// Reset the Binding to the specific column. The default binding is to the DataRowView.
BindingOperations.SetBinding(cp, ContentPresenter.ContentProperty, new Binding(this.ColumnName));
return cp;
}
}

Resources