I've got a WPF application using Caliburn.Micro. I want to be able to overlay the application with a shadow and progress ring (from MahApps.Metro) when I want the application to wait for some work to be done in the background.
What I have at the moment actually works but the overlay is always-on at design time. My ShellView window looks like this:
<Window ...>
...
<Grid>
...
<Rectangle x:Name="waitShadow" Fill="#3f000000" Stroke="Black" StrokeThickness="0" Visibility="{Binding IsWaiting, Converter={StaticResource BooleanToVisibilityConverter}}" Grid.RowSpan="2"/>
<ContentControl ... Visibility="{Binding IsWaiting, Converter={StaticResource BooleanToVisibilityConverter}}">
<Controls:ProgressRing ...> <!-- from MahApps.Metro -->
</Controls:ProgressRing>
</ContentControl>
</Grid>
</Window>
My ShellViewModel class has a public bool property IsWaiting and when I set it to true the shadow and ring comes up and everything is disabled. When I set it to false it goes back to normal, so the binding works (I'm using Fody with the PropertyChanged addin). The only problem is that the Visibility property isn't collapsed at design time.
Is there a better way to have an overlay that works at design time?
You can set a FallbackValue on your binding, that will Collapse it in design time
Visibility="{Binding IsWaiting, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}"
You could also make IsWaiting a DependancyProperty and set the default there, But I find this the easiest solution.
FallbackValue doesn't always work, i.e. if your designer is actually bound to design time data, and FallbackValue actually modifies run-time behaviour of the binding which may be less than desirable in many situations. I made a markup extension that lets designers fiddle with the UI in the designer without worrying about messing up run-time behaviour. I wrote about it here: http://www.singulink.com/CodeIndex/post/wpf-visibility-binding-with-design-time-control
It can be used like this:
<Grid Visibility="{data:Value {Binding RootObject, Converter={StaticResource NullToVisibilityConverter}}, DesignValue=Visible}">
<TextBlock Background="Red" Text="Testing visibility" />
</Grid>
The code for ValueExtension is as follows (any updates or bug fixes will be posted to the blog so I suggest checking there for the latest version):
public class ValueExtension : MarkupExtension
{
public object DesignValue { get; set; } = DependencyProperty.UnsetValue;
[ConstructorArgument("value")]
public object Value { get; set; } = DependencyProperty.UnsetValue;
public ValueExtension() { }
public ValueExtension(object value)
{
Value = value;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var property = provideValueTarget.TargetProperty as DependencyProperty;
var target = provideValueTarget.TargetObject as DependencyObject;
if (target == null || property == null)
return this;
object value = DesignerProperties.GetIsInDesignMode(target) && DesignValue != DependencyProperty.UnsetValue ? DesignValue : Value;
if (value == DependencyProperty.UnsetValue || value == null)
return value;
if (value is MarkupExtension)
return ((MarkupExtension)value).ProvideValue(serviceProvider);
if (property.PropertyType.IsInstanceOfType(value))
return value;
return TypeDescriptor.GetConverter(property.PropertyType).ConvertFrom(value);
}
}
Related
I am trying to make a program that gives you a CRUD interface for List of any objects you give it. That includes:
Showing all of their properties inside a ListBox
The ability to insert a new object
The ability to update an object
The ability to delete an object
Keep in mind that, at the compile-time, I have no idea what kind of objects I am getting. For example, I want to have a TextBlock for each of the properties simply listed inside ListBox's DataTemplate. So how would I do the data binding if I don't know the name of the property? Also, how would I generate an insertion form when I don't know property names?
And finally, is it possible to do it using pure MVVM Pattern, without any Code-Behind?
Thanks
One option: Wrap PropertyInfo in a PropertyInfoViewModel so you can bind to it's value:
class PropertyInfoViewModel
{
Object CRUDObject { get; set; }
PropertyInfo PropertyInfo { get; set; }
Object Value {
get
{
return PropertyInfo.GetValue(CRUDObject);
}
set
{
PropertyInfo.SetValue(CRUDObject, value);
}
}
}
You could have an ObservableCollection in your CRUDObjectViewModel, populated when you create it or change the CRUD it's attached to (Look up reflection if confused by this).
Use a template selector to choose a particular editor to display for the PropertyInfoViewModel:
public class PropertyTypeTemplateSelector : DataTemplateSelector
{
public DataTemplate BooleanTemplate { get; set; }
public DataTemplate GuidTemplate { get; set; }
public DataTemplate StringTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
PropertyInfo propertyInfo = (item as PropertyInfoViewModel).PropertyInfo;
if (propertyInfo.PropertyType == typeof(Boolean))
{
return BooleanTemplate;
}
else if (propertyInfo.PropertyType == typeof(Guid))
{
return GuidTemplate;
}
else if (propertyInfo.PropertyType == typeof(String))
{
return StringTemplate;
}
return null;
}
}
You could use it like this:
<ListBox ItemsSource="{Binding Properties}">
<ListBox.Resources>
<DataTemplate x:Key="BooleanTemplate">
<CheckBox Content="{Binding PropertyInfo.Name}" IsChecked="{Binding Value}"/>
</DataTemplate>
<DataTemplate x:Key="GuidTemplate">
<StackPanel>
<TextBox Text="{Binding PropertyInfo.Name}"/>
<TextBox Text="{Binding Value, ValueConverter={StaticResources MyGuidConverter}}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="StringTemplate">
<StackPanel>
<TextBox Text="{Binding PropertyInfo.Name}"/>
<TextBox Text="{Binding Value}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="Null"/>
</ListBox.Resources>
<ListBox.ItemTemplateSelector>
<helpers:PropertyTypeTemplateSelector BooleanTemplate="{StaticResource BooleanTemplate}"
GuidTemplate="{StaticResource GuidTemplate}"
StringTemplate="{StaticResource StringTemplate}"/>
</ListBox.ItemTemplateSelector>
</ListBox>
Might have to think about how to deal with changes/updates though, as this isn't using NotifyPropertyChanged to keep the UI up to date.
I've not tested any of this, but it should work, I think.
This control WPFCrudControl may fit with your problem.
A generic WPF CrudControl implemented based on the MVVM pattern. It gives a huge productivity boost for straightforward CRUD operations (Add, Edit, Delete, Validate, Listing with sorting, paging and searching). The control abstracts both the UI and business logic, so it requires relatively minimal coding effort, while keeping it possible to customize its behavior.
I am using a DataTemplateSelector inside a ContentControl. I have 3 different DataTemplates based on 3 different object types. When I set the content of my ContentControl to data of the mentioned types, the DataTemplateSelector swaps to the specific DataTemplate AND the selector futhermore seems to rollback/reset the values from the old template. Why is that so?
Edit: I figured out that the values get resetted because I have an attached property caled Prop and inside its OnPropertyChangedCallback it notifies me about the Prop having value null on swapping between DataTemplates. You can see that attached property in code below.
Can somebody help me out what happens behind this swapping mechanism of DataTemplateSelector?
Here is a deeper explaination with code:
public void Window1()
{
InitalizeComponents();
}
public void OnClick(object sender, RoutedEventArgs e)
{
if(this.DataContext == null)
this.DataContext = "Hallo";
else{
if(this.DataContext is string)
this.DataContext = 123;
else{
if(this.DataContext is int)
this.DataContext = null;
}
}
}
By clicking on Button few times I change the type and so in ContentControl the selector changes to DataTemplate.
The selector looks like this below. It swaps between textDataTemplate and numericDataTemplate and one more template. As I mentioned i have those three type which are string, int, and one more, that i wish not to metion. Their DataTemplates are called textDataTemplate, numericDataTemplate and that one more. :)
<local:MyTemplateSelector x:Key="dataTemplateSelector"
TextTemplate="{StaticResource textDataTemplate}"
NumericTemplate="{StaticResource numericDataTemplate}"/>
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate TextTemplate;
public DataTemplate NumericTemplate;
public DataTemplate Select(object item, Culture.....)
{
if(item is string)
{
return this.TextTemplate;
}
else
{
return this.NumericTemplate;
}
}
}
ContentControl and XAML looks like this:
<Button Click="OnClick" Content="Click Me"/>
<ContentControl Name="contentCtrl"
Content="{Binding}"
Width="123"
ContentTemplateSelector="{StaticResource dataTemplateSelector}" />
And this is how textDataTemplate looks alike.
<DataTemplate x:Key="textDataTemplate">
<TextBox x:Name="text" my:AttProperties.Prop="{extension:MarkupExt value}" Text="{Binding Path=Txt, Mode=Default, UpdateSourceTrigger=Explicit}"/>
</DataTemplate>
numericDataTemplate looks similar to textDataTemplate just that only digits are allowed.
The Prop is my attached property from AttProperties class of type string. The Prop is somewhere inside of all three DataTemplate. Above the Prop is sitting on a TextBox but it could be a Label too. The markupextension is just a "return Hello". The extension is just there to test how to create a custom markupextension. There is no big deal with the extension. It shouldnt have to do much with the swapping of DataTemplates.
One more time to explain my problem. Swapping seems reselts/rollback my old templates. I swap from textDataTemplate to lets say numericDataTemplate and the Prop of textDataTemplate gets set to null but the value before was "Hello".
Why is that happening? It seems like the same behavior with using tiggers. Once a Trigger is no more valid it rollsback the used values. Is a DataTemplateSelector using some kind of same mechanism as Triggers?
Edited:
The attached property is just a simple .RegisterAttached with an OnPropertyChangedCallback. Inside OnPropertyChangedCallback I figured the prop is null when swapping the dataTemplates.
If you use two-way binding in numeric template and it only accepts something like Double, it can set value to number. But no one can be sure without seeing full code. It's possible that your own code does something wrong.
To understand things better, create your own control, derived from the ContentControl, and use it in your sample. Then override control methods OnContentxxxChanged, insert breakpoints there and debug your application. You should understand, what's going on with your data and with template selector. When application stops on breakpoint, carefully check all values and look at stack trace. To debug bindings you can insert IValueConverters, it would give you place in code, where you can check values.
I really suggest you to make the simplest working thing first, and then go to more complicated things such as textboxes with two-way bindings to some property of some control which you didn't show in your question. Here is a working version with TextBlocks:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void OnClick(object sender, RoutedEventArgs e)
{
if (this.DataContext == null)
this.DataContext = "Hallo";
else if (this.DataContext is string)
this.DataContext = 123;
else if (this.DataContext is int)
this.DataContext = null;
}
}
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate TextTemplate {get; set;}
public DataTemplate NumericTemplate {get; set;}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is string)
{
return this.TextTemplate;
}
else
{
return this.NumericTemplate;
}
}
}
and xaml:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="numericDataTemplate">
<TextBlock Foreground="Red" Text="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="textDataTemplate">
<TextBlock Foreground="Green" Text="{Binding}"/>
</DataTemplate>
<local:MyTemplateSelector x:Key="dataTemplateSelector"
TextTemplate="{StaticResource textDataTemplate}"
NumericTemplate="{StaticResource numericDataTemplate}"/>
</Grid.Resources>
<StackPanel>
<Button Click="OnClick" Content="Click Me" VerticalAlignment="Top"/>
<ContentControl Name="contentCtrl"
Content="{Binding}"
Width="300" Height="100"
ContentTemplateSelector="{StaticResource dataTemplateSelector}" />
</StackPanel>
</Grid>
Compare with your code. When you inherit from DataTemplateSelector, you should override SelectTemplate method and don't invent methods with other names. All controls such as ContentControl will only use SelectTemplate. Etc..
Obviously, all works and DataTemplateSelector does nothing wrong. I suppose, your problem is somewhere in your data and bindings
And look at your OnClick method - it always sets DataContext to null
I'm having some issues with binding some custom controls in a Windows Phone app right now. Usually this is never an issue but apparently my mind can't comprehend this today.
So I'm doing an MVVM style setup which is good. I have my page with a view and also a viewmodel. Now on a WebClient callback I assign the dataContext of my view to the list of models in my ViewModel, nice and simple thus far...now in my view I created a ListBox with a custom control in the datatemplate which is basically a cell in the list. I once again set my user controls dataContext to binding, and binding all the models values to the regular UI elements works no problem.
Here's a sample:
<Grid Grid.Column="0">
<Image Source="{Binding SmallPath}" VerticalAlignment="Top"/>
</Grid>
<Grid Grid.Column="1">
<StackPanel Margin="12,0,0,0">
<TextBlock x:Name="MemberId_TextBlock" Text="{Binding MemberId}" FontSize="28"
Margin="0,-8,0,0"
Foreground="{StaticResource PhoneForegroundBrush}"/>
<StackPanel Orientation="Horizontal" Margin="0,-11,0,0">
<TextBlock Text="{Binding DaysReported}" FontSize="42"
Margin="0,0,0,0"
Foreground="{StaticResource PhoneAccentBrush}"/>
<TextBlock Text="days" FontSize="24"
Margin="3,19,0,0"
Foreground="{StaticResource PhoneSubtleBrush}"/>
</StackPanel>
</StackPanel>
</Grid>
That's in my user control, and here's the the view where the usercontrol is housed:
<Grid x:Name="LayoutRoot" Background="Transparent">
<ListBox Name="TopSpotter_ListBox" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<!--<TextBlock Text="{Binding MemberId}"/>-->
<controls:TopSpotterItemControl DataContext="{Binding}"/>
<Grid Height="18"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Now this is good enough but what I want to do in my view is set data from my model like Booleans that determine whether or not I should show certain Grids etc. So if I try to set a dependency property explicitly in my control it fires and will run logic in the Getter/Setters for instance. HOWEVER if I try to set these custom objects from a binding source it won't actually set.
Here's what works:
<controls:TopSpotterItemControl ChampVisibility="True">
This way will trigger the ChampVisibility property and then in the code behind of the user control I can set visibilities.
Here's what fails but I want to work:
<controls:TopSpotterItemControl ChampVisibility="{Binding IsChamp">
In addition I can still set the DataContext to {Binding} and the result will be unchanged.
In this scenario IsChamp is part of my model that I would like to bind to this user control which I guess comes from the dataContext being set on the view from the viewModel. I'm not sure what I can do to get this so the bindings work etc. without having to set custom properties.
Finally, here's my user control:
public partial class TopSpotterItemControl : UserControl
{
public string MemberId
{
get
{
return this.MemberId_TextBlock.Text;
}
set
{
this.MemberId_TextBlock.Text = value;
}
}
public bool ChampVisibility {
set
{
if (value)
{
this.Champ_Grid.Visibility = System.Windows.Visibility.Visible;
}
}
}
public static readonly DependencyProperty MemberNameProperty =
DependencyProperty.Register("MemberId", typeof(string), typeof(TopSpotterItemControl), new PropertyMetadata(null));
public static readonly DependencyProperty ChampVisibilityProperty =
DependencyProperty.Register("ChampVisibility", typeof(bool), typeof(TopSpotterItemControl), new PropertyMetadata(null));
public TopSpotterItemControl()
{
InitializeComponent();
}
}
Bit long winded and I hope I made things on the issue clear. My one major hang up so far, and I'd like to abstract as much control as I can to the user control via dependency properties explicitly set in xaml, rather than setting up binding in its xaml that depend on the knowledge of a model. Thanks!
Your DependencyProperty is badly formed. (I also don't see Champ_Grid defined in your class or XAML, but I assume that is an ommission)
Setting ChampVisibility = true in code works because it is unrelated to the DependencyProperty.
You can tell easily because the default value for your DP is invalid. It will compile, but the instance constructor will through an exception if it is ever invoked.
new PropertyMetadata(null)
bool = null = exception
If you call GetValue(TopSpotterItemControl.ChampVisibilityProperty) from somewhere you can confirm all of the above.
You should make changes to instance fields in the property changed handler and declare the property like the following, it will work:
Note that the property has to change (not just be set) for the event to be raised.
public bool ChampVisibility
{
get { return (bool)GetValue(ChampVisibilityProperty); }
set { SetValue(ChampVisibilityProperty, value); }
}
public static readonly DependencyProperty ChampVisibilityProperty =
DependencyProperty.Register("ChampVisibility ", typeof(bool), typeof(TopSpotterItemControl), new PropertyMetadata(true, (s, e) =>
{
TopSpotterItemControl instance = s as TopSpotterItemControl;
instance.Champ_Grid.Visibility = instance.ChampVisibility ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
}));
Incidentally, your MemberId DependencyProperty is also completely wrong and cannot work.
Note:
The Binding on your TextBox works, because it is binding to the DataContext (your model), so it probably shows the right value.
The Dependency property in your UserControl will never be set though.
Use the propdp code-snippet in Visual Studio so you dont have to concern yourself with the complexities of Dependency Property declaration.
Also check this out for more info about Dependency Properties
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....
I can't get a checked ListBox to work.
My business object (it's a private/nested class, hence the lower-case)
class shop : System.ComponentModel.INotifyPropertyChanged
{
internal int id;
string _name;
internal string name
{
get { return _name; }
set
{
_name = value;
if (PropertyChanged != null) PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("name"));
}
}
bool _selected;
internal bool selected
{
get { return _selected; }
set
{
_selected = value;
if (PropertyChanged != null) PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("selected"));
}
}
}
My XAML:
<ListBox ItemsSource="{Binding}" Grid.Row="1" HorizontalAlignment="Stretch" Margin="10,0,10,0" Name="lbSelectedShops" VerticalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Width="Auto" Content="{Binding Path=name}" IsChecked="{Binding Path=selected, Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Databinding in code behind is very simple:
lbSelectedShops.ItemsSource = _shops;
where _shops is an ObservableCollection<shop> (containing two elements).
What I get is two blank checkboxes in the listbox (no captions, and both ticked off, even though selected is set to true for all items in the ItemsSource).
I'm really frustrated already and I'm sure it must be something very trivial. What is wrong here?
It's not working because your properties are internal and for Databinding you need public properties.
From MSDN (Binding Sources Overview):
You can bind to public properties, sub-properties, as well as
indexers, of any common language runtime (CLR) object. The binding
engine uses CLR reflection to get the values of the properties.
Alternatively, objects that implement ICustomTypeDescriptor or have a
registered TypeDescriptionProvider also work with the binding engine.
Binding work only with public properties (and public classes)