WPF Binding DataTable Row Column to textbox - wpf

NARROWED DOWN SOLUTION
I'm much closer, but don't know how to apply XAML to change datacontext value. Please review context of original question below as may be needed.
My issue is that I have a ViewModel class as the datacontext to a window. On this view model, I have a "DataTable" object (with columns and just a single row for testing). When I try to set a Textbox "TEXT" binding to the column of the datatable, it doesn't work. What I've ultimately found is that no matter what "source" or "path" I give it, it just won't cooperate. HOWEVER, just by playing around with scenarios, I said the heck with it. Lets look. The Textbox control has its own "DataContext" property. So, in code, I just FORCED the textbox.DataContext = "MyViewModel.MyDataTableObject" and left the path to just the column it should represent "MyDataColumn", and it worked.
So, that said, how would I write the XAML for the textbox control so it's "DataContext" property is set to that of the datatable object of the view model the window but can't get that correct. Ex:
<TextBox Name="myTextBox"
Width="120"
DataContext="THIS IS WHAT I NEED" --- to represent
Text="{Binding Path=DataName,
ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged }" />
DataContext for this textbox should reflect XAML details below and get
(ActualWindow) ( DDT = View Model) (oPerson = DataTable that exists ON the view model)
CurrentWindow.DDT.oPerson
I'm stuck on something with binding. I want to bind a column of a datatable to a textbox control. Sounds simple, but I'm missing something. Simple scenario first. If I have my window and set the data context to that of "MyDataTable", and have the textbox PATH=MyDataColumn, all works fine, no problems, including data validation (red border on errors).
Now, the problem. If I this have a same "MyDataTable" as a public on my Window Class directly (but same thing if I had it on an actual ViewModel object, but the window to simplify the level referencing), I can't get it to work from direct XAML source. I knew I had to set the "SOURCE=MyDataTable", but the path of just the column didn't work.
<TextBox Name="myTextBox"
Text="{Binding Source=DDT, Path=Rows[0][DataName],
ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged }" />
However, from other testing, if I set the path (in code-behind) to
object txt = FindName("myTextBox");
Binding oBind = new Binding("DataName");
oBind.Source = DDT;
oBind.Mode = BindingMode.TwoWay;
oBind.ValidatesOnDataErrors = true;
oBind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
((TextBox)txt).SetBinding(TextBox.TextProperty, oBind);
It DOES work (when the datatable is available as public in the window (or view model))
What am I missing otherwise.
UPDATE: HERE IS A FULL POST of the sample code I'm applying here.
using System.ComponentModel;
using System.Data;
namespace WPFSample1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public DerivedDataTable DDT;
public MainWindow()
{
InitializeComponent();
// hook up to a Data Table
DDT = new DerivedDataTable();
DataContext = this;
// with THIS part enabled, the binding works.
// DISABLE this IF test, and binding does NOT.
// but also note, I tried these same settings manually via XAML.
object txt = FindName("myTextBox");
if( txt is TextBox)
{
Binding oBind = new Binding("DataName");
oBind.Source = DDT;
oBind.Mode = BindingMode.TwoWay;
oBind.ValidatesOnDataErrors = true;
oBind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
((TextBox)txt).SetBinding(TextBox.TextProperty, oBind);
}
}
}
// Generic class with hooks to enable error trapping at the data table
// level via ColumnChanged event vs IDataErrorInfo of individual properties
public class MyDataTable : DataTable
{
public MyDataTable()
{
// hook to column changing
ColumnChanged += MyDataColumnChanged;
}
protected void MyDataColumnChanged(object sender, DataColumnChangeEventArgs e)
{ ValidationTest( e.Row, e.Column.ColumnName); }
// For any derived datatable to just need to define the validation method
protected virtual string ValidationTest(DataRow oDR, string ColumnName)
{ return ""; }
}
public class DerivedDataTable : MyDataTable
{
public DerivedDataTable()
{
// simple data table, one column, one row and defaulting the value to "X"
// so when the window starts, I KNOW its properly bound when the form shows
// "X" initial value when form starts
Columns.Add( new DataColumn("DataName", typeof(System.String)) );
Columns["DataName"].DefaultValue = "X";
// Add a new row to the table
Rows.Add(NewRow());
}
protected override string ValidationTest(DataRow oDR, string ColumnName)
{
string error = "";
switch (ColumnName.ToLower())
{
case "dataname" :
if ( string.IsNullOrEmpty(oDR[ColumnName].ToString() )
|| oDR[ColumnName].ToString().Length < 4 )
error = "Name Minimum 4 characters";
break;
}
// the datarow "SetColumnError" is what hooks the "HasErrors" validation
// in similar fashion as IDataErrorInfo.
oDR.SetColumnError(Columns[ColumnName], error);
return error;
}
}
}
AND here's the XAML. Any brand new form and this is the only control in the default "grid" of the window.
Tried following versions, just defining the Rows[0][Column]
<TextBox Name="myTextBox"
Width="120"
Text="{Binding Path=Rows[0][DataName],
ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged }" />
Including the source of "DDT" since it is public to the window
<TextBox Name="myTextBox"
Width="120"
Text="{Binding Source=DDT, Path=Rows[0][DataName],
ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged }" />
And even suggestions offered by grantnz

I think your xaml is setting the source to the string "DDT" when you're expecting it to be the property DDT on the current window.
Do you see an error in the output window of Visual Studio like:
System.Windows.Data Error: 40 : BindingExpression path error:
'Rows' property not found on 'object' ''String' (HashCode=1130459074)'.
BindingExpression:Path=Rows[0][DataName]; DataItem='String' (HashCode=1130459074);
target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
If you set the window DataContext to this (from code DataContext = this; or xaml), you can use:
Text="{Binding Path=DDT.Rows[0][DataName],
ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged }" />
or you can leave the DataContext as null and use:
<TextBox Name="myTextBox"
Text="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}},Path=DDT.Rows[0][DataName],
ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged }" />
The above assumes that you are setting the DDT property before the binding is set-up. If DDT is set after the binding is configured, you'll need to implement INotifyPropertyChanged.
Here's the source of a working version (with DataContext set from XAML and INotifyPropertyChanged implemented). It doesn't work if you comment out the line
OnPropertyChanged(new PropertyChangedEventArgs("DDT"));
and the second TextBox is bound if you leave out the following out of the XAML
DataContext="{Binding RelativeSource={RelativeSource Self}}"
CODE
public partial class MainWindow : Window, INotifyPropertyChanged
{
public DataTable DDT { get; set; }
public String SP { get; set; }
public MainWindow()
{
InitializeComponent();
DDT = new DerivedDataTable();
OnPropertyChanged(new PropertyChangedEventArgs("DDT"));
SP = "String prop";
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
}
XAML
<Window x:Class="BindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<TextBox
Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=DDT.Rows[0][DataName],
ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged }" />
<TextBox
Text="{Binding Path=DDT.Rows[0][DataName],
ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged }" />
<TextBox
Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=SP}" />
</StackPanel>
</Window>

SOLVED, but what a PITA... Most things within the samples of doing MVVM patterns will have properties on the view model exposing whatever you want to hook into. When dealing with binding to a DATATABLE (or similar view, etc), you are binding to COLUMNs of said table (or view).
When a table is queried from whatever back-end, the schema populating the data columns will always force the column names to UPPER CASE.
So, if you have a column "InvoiceTotal" in your table, when queried, the column name will have it as "INVOICETOTAL".
If you try to bind to the
Path="InvoiceTotal" ... it will fail
Path="INVOICETOTAL" ... it WILL WORK
However, if you are working directly in .Net (I use C#), the following will BOTH get a value back from the row
double SomeValue = (double)MyTable.Rows[0]["InvoiceTotal"];
or
double SomeValue = (double)MyTable.Rows[0]["INVOICETotal"];
or
double SomeValue = (double)MyTable.Rows[0]["invoicetotal"];
all regardless of the case-sensitivity of the column name.
So, now the rest of the bindings, Error triggers available at the table, row or column levels can properly be reflected in the GUI to the user.
I SURE HOPE this saves someone else the headaches and research I have gone through on this....

Related

Difference Source and Datacontext in wpf

DataContext and Source seem to be very similar to me.
What are the advantages and disadvantages?
When to use which one?
With Source:
<TextBlock Text="{Binding Name, Source={StaticResource Person}}" />
Or the solution with DataContext:
public partial class DataContextSample : Window
{
public string Name {get; set;}
public DataContextSample()
{
InitializeComponent();
this.DataContext = this;
}
}
<TextBlock Text="{Binding Name}" />
A binding with out a specified Source binds to the DataContext property of the element.
The DataContext is a special property which, if not set, is redirected to the element's parent's DataContext. This prevents duplicate xaml (always setting the Source in every binding) and makes all bindings relative so it is easier to change the UI without having to adjust all Sources in the bindings.

Using a variable in XAML binding expression

I'm building a control that can edit POCOs. There is a descriptor collection for the fields within the POCO that need to be edited and I'm binding a ListBox's ItemsSource to this collection. Amongst other things, the descriptor gives me the ability to select a suitable DataTemplate and the variable name in the POCO that this ListBox item should edit.
My ListBox is built like this:
<ListBox ItemsSource="{Binding ColumnCollection, ElementName=root}">
<ListBox.Resources>
<DataTemplate x:Key="TextTemplate">
<StackPanel>
<TextBlock Text="{Binding DisplayName}" />
<!-- !!! Question about following line !!! -->
<TextBox Text="{Binding ElementName=vm.CurentEditing, Path=PathName}" />
</StackPanel>
</DataTemplate>
<!-- Details omitted for brevity -->
<DataTemplate x:Key="PickListTemplate" />
<DataTemplate x:Key="BooleanTemplate" />
</ListBox.Resources>
<ListBox.ItemTemplateSelector>
<local:DataTypeSelector
TextTemplate="{StaticResource TextTemplate}"
PickListTemplate="{StaticResource PickListTemplate}"
BooleanTemplate="{StaticResource BooleanTemplate}"
/>
</ListBox.ItemTemplateSelector>
</ListBox>
It is the TextBox binding expression in the "TextTemplate" that I am having problems with. The problem is that "PathName" should not be taken as a literal string, but is the name of a string property in the ColumnDescription class (the collection type of ColumnCollection used for ListBox.ItemsSource), which gives the name of the POCO property I want to bind to (the POCO is "vm.CurrentEditing").
Is there some way to use the value of a property in XAML as input to a binding expression, or will I have to resort to code behind?
(Incidentally, specifying the ElementName as "x.y" as I have done above also seems to be invalid. I assume the "y" part should be in Path but that's currently taken up with my property name...!)
So you want to bind TextBox.Text to Property X of Object Y, where X and Y both change at runtime.
It sounds like what you want to do is something analogous to ListBox.DisplayMemberPath: You can bind a string or PropertyPath property to DisplayMemberPath and it'll work. The way I've done stuff like that is to have a dependency property of type String or PropertyPath, and programatically create a binding from that to whatever property.
So, I wrote an attached property which creates a binding.
public class POCOWrangler
{
#region POCOWrangler.BindPropertyToText Attached Property
public static String GetBindPropertyToText(TextBox obj)
{
return (String)obj.GetValue(BindPropertyToTextProperty);
}
public static void SetBindPropertyToText(TextBox obj, PropertyPath value)
{
obj.SetValue(BindPropertyToTextProperty, value);
}
public static readonly DependencyProperty BindPropertyToTextProperty =
DependencyProperty.RegisterAttached("BindPropertyToText", typeof(String), typeof(POCOWrangler),
new PropertyMetadata(null, BindPropertyToText_PropertyChanged));
private static void BindPropertyToText_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is String && d is TextBox)
{
var tb = d as TextBox;
var binding = new Binding((String)e.NewValue);
// The POCO object we're editing must be the DataContext of the TextBox,
// which is what you've got already -- but don't set Source explicitly
// here. Leave it alone and Binding.Source will be updated as
// TextBox.DataContext changes. If you set it explicitly here, it's
// carved in stone. That's especially a problem if this attached
// property gets initialized before DataContext.
//binding.Source = tb.DataContext;
binding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(tb, TextBox.TextProperty, binding);
}
}
#endregion POCOWrangler.BindPropertyToText Attached Property
}
And I wrote a quick example thing: There's a little class named Foo that has a Name property, and a viewmodel with two properties, Foo Foo and String DisplayPathName. It works! Of course, this depends on default TextBox editing behavior for whatever type the property happens to be. I think that will get you the same results as if you'd bound explicitly in XAML, but it sitll won't always necessarily be just what you want. But you could very easily go a little nuts and add some triggers in the DataTemplate to swap in different editors, or write a DataTemplateSelector.
I stuffed ViewModel.Foo in a ContentControl just to get a DataTemplate into the act, so that the TextBox gets his DataContext in the same manner as yours.
Note also that I'm getting DisplayPathName by a relative source from something outside the DataContext object -- it's not a member of Foo, of course, it's a member of the viewmodel.
C#
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel {
DisplayPathName = "Name",
Foo = new Foo { Name = "Aloysius" }
};
}
XAML
<ContentControl
Content="{Binding Foo}"
>
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBox
local:POCOWrangler.BindPropertyToText="{Binding
DataContext.DisplayPathName,
RelativeSource={RelativeSource AncestorType=ContentControl}}"
/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
That was fun.

Changing content changes DataContext when using DataTemplate

X problem.
I want to enlarge (let it take whole available space) a part of window content temporarily.
Window layout is pretty complicated: many nested panels, splitters, content to enlarge is 10 levels deep. Changing Visibility to stretch content is simply not enough (thanks to splitters) and seems very complicated.
Y problem
I decide to move that content into a user control and do something like (pseudo-code)
if(IsEnlarged)
{
oldContent = window.Content; // store
window.Content = newContent;
}
else
window.Content = oldContent; // restore
No problems. It was working perfectly in test project ... until I start using data templates.
Problem: if data templates are used then as soon as window.Content = newContent occurs, then that newContent.DataContext is lost and become the same as window.DataContext. This will trigger various binding errors, attached behaviors suddenly changes to default value, etc.. all kind of bad stuff.
Question: why DataContext is changing? How to prevent/fix this issue?
Here is a repro (sorry, can't make it any shorter):
MainWindow.xaml contains
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel}">
<local:UserControl1 />
</DataTemplate>
</Window.Resources>
<Grid Background="Gray">
<ContentControl Content="{Binding ViewModel}" />
</Grid>
MainWindow.cs contains
public ViewModel ViewModel { get; } = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
UserControl1.xaml contains
<Button Width="100"
Height="100"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Command="{Binding Command}" />
ViewModel (using DelegateCommand)
public class ViewModel
{
public DelegateCommand Command { get; set; }
bool _set;
object _content;
public ViewModel()
{
Command = new DelegateCommand(o =>
{
var button = (Button)o;
var window = Window.GetWindow(button);
_set = !_set;
if (_set)
{
_content = window.Content;
var a = button.DataContext; // a == ViewModel
window.Content = button;
var b = button.DataContext; // b == MainWindow ??? why???
}
else
window.Content = _content;
});
}
}
Set breakpoint on var a = ..., start program, click the button, do steps and observe button.DataContext as well as binding error in Output window.
ok here some general thoughts.
if you bind a object(viewmodel) to the Content property of the contentcontrol then wpf uses a DataTemplate to visualize the object. if you dont have a DataTemplate you just see object.ToString(). DataContext inheritance means that the DataContext is inherited to the child elements. so a real usercontrol will inherit the DataContext from the parent. the are common mistakes you find here on stackoverflow when creating UserControls - they often break DataContext inheritance and set the DataContext to self or a new DataContext.
UserControl DataContext Binding
You must be trying to use your DataTemplate as ContentTemplate for your ContentControl. As ContentTemplate operates on Content, so it will use Content as its DataContext. And your Content contains ViewModel.
Once your Button is no longer part of your DataTemplate, so it will use MainWindow's DataContext.
No seeing your comments, I am assuming that you want DataContext of UserControl to remain intact, even if your UserControl is not part of DataTemplate.
So, simple set DataContext of Button explicitly using XAML using RelativeSource.
Example,
<Button Content="{Binding Data}" DataContext="{Binding vm1, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" />
Playing with DataContext in code is not a good idea.

How to bind local variable in WPF

I have silverlight usercontrol. This contains Service Entity object. see below
public partial class MainPage : UserControl
{
public ServiceRef.tPage CurrentPage { get; set; }
...
}
I need to bind CurrentPage.Title to TextBox
My xaml is here
<TextBox Text="{Binding Path=CurrentPage.Title, RelativeSource={RelativeSource self}}"></TextBox>
But it is not work.
How to do it?
In order for that to work, you'll have to implement INotifyPropertyChanged on your class and raise the PropertyChanged event for CurrentPage when it's set (this also means you won't be able to use auto properties; you'll have to use your own private instance backing variable and code the get { } and set { } yourself).
What's happening is the control is binding to the value before you've set CurrentPage. Because you aren't notifying anyone that the property has changed, it does not know to refresh the bound data. Implementing INotifyPropertyChanged will fix this.
Or you could just manually set the Text property yourself in the setter.
Change your markup to
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=CurrentPage.Title}" />
By assigning RelativeSource={RelativeSource self} your are telling the TextBlock to bind to itself and look for a property named CurrentPage on the TextBlock itself and not the parent Window.
set the UpdateSourceTrigger="PropertyChanged" in the XAML.

ListBox doesn't refresh after property changed

I'm trying to bind two ListBoxes:
<ListBox SelectionChanged="lbApplications_SelectionChanged"
ItemsSource="{Binding Path=Applications,
UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
<ListBox DisplayMemberPath="Message"
ItemsSource="{Binding Path=Events,
UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
Applications and Events are public properties in Window class.
I set DataContext to this to both list boxes and implement INotifyPropertyChanged in Window class:
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
And then after adding new item to Applications or Events I call:
NotifyPropertyChanged("Events");
NotifyPropertyChanged("Applications");
The issue is that ListBox is loaded only one time. What am I doing wrong?
Let's just look at one of the ListBoxes, since they're both the same, basically.
The code we're concerned about is this:
<ListBox ItemsSource="{Binding Path=Applications,
UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
Since you're new to WPF, let me say you probably don't need UpdateSourceTrigger or Mode in there, which leaves us with this:
<ListBox ItemsSource="{Binding Path=Applications}" />
You mentioned that Applications is a public property in your code-behind. You need it to be a DependencyProperty, and you need it to fire events when it changes -- most people use an ObservableCollection for this.
So your code-behind will have something like this:
public ObservableCollection<string> Applications
{
get { return (ObservableCollection<string>)GetValue(ApplicationsProperty); }
set { SetValue(ApplicationsProperty, value); }
}
public static readonly DependencyProperty ApplicationsProperty =
DependencyProperty.Register("Applications",
typeof(ObservableCollection<string>), typeof(Window1),
new UIPropertyMetadata(null));
Then, where you want to add it, you'll do something like this:
this.Applications = new ObservableCollection<string>();
Applications.Add("Whatever");
Finally, for the "simple" binding syntax to work in the XAML, I usually change the DataContext in my Window (or the root Control element for the file, whatever I'm working in) to
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}" ... >
...
Your Applications box will update automatically.
The problem is that your property value hasn't changed. It's still the same list, same reference.
One solution might be that your collections are of type ObservableCollection. These lists provide events for WPF when you add or remove items.

Resources