Easy way to cascade binding updates - c#

Just hit a snag in my MVVM application that's made me wonder if I'm doing MVVM "right", or if I've missed a trick somewhere. Consider a situation like this on the viewmodel:
private _bookings ObservableCollection<Booking>;
public ObservableCollection<Booking> Bookings
{
get { return _bookings; }
set
{
_bookings = value;
OnPropertyChanged("Bookings");
}
}
public int Requested
{
get
{
return (int)Bookings.Sum(g => g.Requested);
}
}
public int Available
{
get
{
return (int)Bookings.Sum(g => g.Volume);
}
}
And then in a datagrid on the view:
<DataGrid DataContext="{Binding Bookings, Mode=TwoWay}" ItemsSource="{Binding SourceCollection}">
<!-- various datagrid related gubbins -->
</DataGrid>
<Label BorderThickness="1" Content="{Binding Requested}"></Label>
<Label BorderThickness="1" Content="{Binding Available}"></Label>
Now, whenever something in the ViewModel changes the contents of the Bookings collection, the contents of my grid will auto-update which is great.
But the Active and Requested properties which are calculated on the contents of that collection don't auto update, and can't because they're read-only. Even if I made each one two way and raised a changed event for them both (which I'm reluctant to do, because in the real-world version there are a lot of these dependent properties), the code chokes on execution because you can't make a two-way binding to a read-only property.
Is there any straightforward way round this problem, ideally to cascade changes to dependent properties and have them auto-update in the view?

Like #dellywheel mentioned in his answer that you need to cascade property changes if you want controls bound to these properties to get change notification.
However, if you are dealing with small class and can afford property changes, you can raise PropertyChanged event with empty string so all properties in class will pass on property change notification to UI. That ways you won't have to raise individual property change events for each properties.
OnPropertyChanged("");

You need to raise the OnPropertyChanged after Bookings collection changes (or wherever you need to in your code).
private _bookings ObservableCollection<Booking>;
public ObservableCollection<Booking> Bookings
{
get { return _bookings; }
set
{
_bookings = value;
OnPropertyChanged("Bookings");
OnPropertyChanged("Requested");
OnPropertyChanged("Available");
}
}

Related

C# wpf INotifyChanged does not called when value is same

I have 2 combobox, which include menus name and its details like (Pie-apple, chocolate), (Juice- apple, orange).
So if I select 'Pie' in the first combobox, second one should have its details-apple and chocolate.
Xaml code is below:
<DockPanel Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" VerticalAlignment="Center" Margin="5">
<TextBlock Text="Menu : " HorizontalAlignment="Left" Height="32"/>
<ComboBox ItemsSource="{Binding LstMenu}" SelectedValue="{Binding SelectedMenu}" HorizontalAlignment="Left" Height="32"/>
<TextBlock Text="Detail: " HorizontalAlignment="Left" Height="32"/>
<ComboBox ItemsSource="{Binding LstDetail}" SelectedValue="{Binding SelectedDetail}" HorizontalAlignment="Left" Height="32" SelectionChanged="combobox_changed"/>
</DockPanel>
and viewmodel is below:
private List<string> lstMenu;
public List<string> LstMenu {
get { return lstMenu; }
set {
lstMenu = value;
RaisePropertyChanged("LstMenu");
}
private string selectedMenu;
public string SelectedMenu {
get { return selectedMenu; }
set {
selectedMenu= value;
RaisePropertyChanged("SelectedMenu");
LstDetails = new ObservableCollection<string>( //get its details list from DB );
}
private ObservableCollection<string> lstDetail;
public ObservableCollection<string> LstDetail {
get { return lstDetail; }
set {
lstDetail= value;
RaisePropertyChanged("LstDetail");
if (lstDetail != null && lstDetail.Count > 0) {
SelectedDetail = lstDetail.FirstOrDefault();
}
}
private string selectedDetail;
public string SelectedDetail {
get { return selectedDetail; }
set {
selectedDetail= value;
RaisePropertyChanged("SelectedDetail");
}
The problem is, if I selected Pie-apple and then changed the first combobox to Juice menu, Juice-apple is automatically selected but RaisePropertyChanged("SelectedDetail"); does not work.
I know that's because the selected two strings apple are same...
However I need to call the event SelectionChanged="combobox_changed" since it contains UI reload event.
Moreover, if I added IsAsync=True into the LstDetail combobox, it definitely works but SelectionChanged="combobox_changed" is called twice. I don't know why.
what should I do?
It's hard to know exactly what the architecture of your program is, without a good Minimal, Reproducible Example. But, from what you've posted, it seems you are trying to do too much of the work, and not letting WPF do its fair share. In particular, you seem to have just one "view model" type, and in that object you attempt to completely define everything that the user sees, and the state of that.
As has been noted in the comments, this has led to a situation where there are pieces of state that relate to each other, but where you haven't done enough work to make sure they stay synchronized with each other. You've delegated some of the work to WPF, but it doesn't have enough context to let you know when something important has changed, and so your UI winds up in a inconsistent state.
It would in fact be possible to fix your program as it stands now, by forcing the SelectedDetail property to refresh itself if the menu combo box changes. But a) I can't tell you exactly how to do that, because you've omitted all of the details that relate to the actual management of that property (such as the combobox_changed() event handler), and b) that's really just too much work anyway.
The first thing you need to get on board with, when writing WPF code, is to put as much of your program specification into declarations, and not procedures. WPF's binding mechanisms do a great job of automatically keeping view model data structures in sync with the actual UI. This means that you can view your program as two completely independent entities: the user interface itself, and the "business logic", i.e. the things your program actually has to do. The "view model" part mediates between these two elements. In the simplest WPF programs, the business logic itself can be entirely encapsulated in the view model data structures; in more complex applications, the view models focus on mapping between the UI ("view") and the business logic ("model").
This has an important implication: if you find yourself writing code that is directly interacting with the view element of your program — i.e. either responding to the UI or modifying it — that code had better be strictly specific to the view. Another way to look at that is, such code should be reusable with any other type of business logic, just as all of the built-in XAML stuff is completely reusable and not at all specific to your business logic.
Conversely, if that code you're writing is fiddling with the view model data structures directly or, even worse, is actually part of the view model data structures, you've gone off into the weeds. This should never happen.
You can use these two metrics to constantly evaluate as you go along whether you're designing the code correctly, and to help guide that design before you actually write the code.
Okay, with that little bit of indoctrination out of the way, here's how I would implement your stated goal:
You need some view models. Not just one, because you have a hierarchy of business logic objects, and the view models need to reflect that. Working from the bottom up:
You need a view model that can represent the detail to be displayed. For example:
class DetailViewModel : NotifyPropertyChangedBase
{
private string _name;
public string Name
{
get => _name;
set => _UpdateField(ref _name, value);
}
private string _description;
public string Description
{
get => _description;
set => _UpdateField(ref _description, value);
}
private decimal _price;
public decimal Price
{
get => _price;
set => _UpdateField(ref _price, value);
}
}
Notes:
The above relies on a base class NotifyPropertyChangedBase I use for all view models, which provides a convenient mechanism to implement observable properties. Code for that is provided below.
The above is strictly a simple data container. For this example, that's all that's needed, because all the example is concerning itself with is how to react to UI input, and WPF is great at managing that already, as long as it has a place to keep everything. A more interesting WPF program would have procedural aspects in the view model for providing commands that operate on the data beyond what the XAML is capable of defining.
Okay, so with a details data structure, we also need a place to keep a list of these objects for each type of menu in your program:
class MenuViewModel : NotifyPropertyChangedBase
{
private string _name;
public string Name
{
get => _name;
set => _UpdateField(ref _name, value);
}
private List<DetailViewModel> _menuItems = new List<DetailViewModel>();
public List<DetailViewModel> MenuItems
{
get => _menuItems;
set => _UpdateField(ref _menuItems, value);
}
private DetailViewModel _selectedItem;
public DetailViewModel SelectedItem
{
get => _selectedItem;
set => _UpdateField(ref _selectedItem, value);
}
}
You'll note that the above two view model data structures have a Name property. This is used to display to the user the name of the item they will be selecting.
The new aspect in this view model is the list of menu item objects, and then a property that keeps track of the currently selected menu item object. This is critical with respect to your question: in your implementation, the only thing you know about the currently selected item is its name. But when the same name appears on two different menus, you've got no way to distinguish the two. The only way out of that dilemma, given the design you chose, is to always refresh the details explicitly when the selected menu changes.
But here, we tie the selected item to the menu itself. This gives us two nice results:
When the menu changes, then whatever's bound to the selected item property will change as well, implicitly updating the displayed details, because WPF's binding engine understands the relationships of the properties involved. In particular, the details aren't just some random string, but rather a specific object that was retrieved from a different specific object. If that latter specific object is no longer the context for the binding (i.e. the user picks a new menu), then WPF knows that the former specific object needs to be re-evaluated.
By default, the user's selection for a given menu is remembered, because each menu has its own SelectedItem property! When the user selects an item from a menu, then selects a different menu, then after they are done with that second menu and go back to the first, the first will still have their previous selection from that menu. Now, this may or may not be the desired behavior. If not, it's reasonably easy in the view model to reset the selected item when the menu changes. But it's usually easier to suppress functionality than to create it, so having the default behavior provide that added functionality is nice.
Finally, of course, we need a place to keep track of the currently selected menu:
class MainViewModel : NotifyPropertyChangedBase
{
private List<MenuViewModel> _menus = new List<MenuViewModel>();
public List<MenuViewModel> Menus
{
get => _menus;
set => _UpdateField(ref _menus, value);
}
private MenuViewModel _selectedMenu;
public MenuViewModel SelectedMenu
{
get => _selectedMenu;
set => _UpdateField(ref _selectedMenu, value);
}
}
Just like with the menu object, this one has both a list of items (menus, in this case) and a property that keeps track of which specific item is selected.
Now that the view model data structures have been correctly designed to reflect the hierarchy of user selection in our user interface, it's a very simple matter to declare the user interface to work with those data structures:
<Window x:Class="TestSO58167153WpfTwoLevelDetail.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:l="clr-namespace:TestSO58167153WpfTwoLevelDetail"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Key="comboBoxNameTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:DetailViewModel}">
<StackPanel>
<TextBlock Text="{Binding Description}"/>
<TextBlock Text="{Binding Price, StringFormat={}Price: ${0:0.00}}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" Grid.Column="0" Width="100"
ItemsSource="{Binding Menus}" SelectedItem="{Binding SelectedMenu}"
ItemTemplate="{StaticResource comboBoxNameTemplate}"/>
<ComboBox Grid.Row="0" Grid.Column="1" Width="100" HorizontalAlignment="Left" Margin="10,0"
ItemsSource="{Binding SelectedMenu.MenuItems}" SelectedItem="{Binding SelectedMenu.SelectedItem}"
ItemTemplate="{StaticResource comboBoxNameTemplate}"/>
<ContentPresenter Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
Content="{Binding SelectedMenu.SelectedItem}"/>
</Grid>
<Window.DataContext>
<l:MainViewModel>
<l:MainViewModel.Menus>
<l:MenuViewModel Name="Pies">
<l:MenuViewModel.MenuItems>
<l:DetailViewModel Name="Apple" Description="Apple Pie with Pastry Crust" Price="10.50"/>
<l:DetailViewModel Name="Grasshopper" Description="Mint Pie with Oreo Crust" Price="17.95"/>
</l:MenuViewModel.MenuItems>
</l:MenuViewModel>
<l:MenuViewModel Name="Juice">
<l:MenuViewModel.MenuItems>
<l:DetailViewModel Name="Apple" Description="Refreshing Apple Juice" Price="3.70"/>
<l:DetailViewModel Name="Mango" Description="Sweet Mango Juice" Price="4.75"/>
</l:MenuViewModel.MenuItems>
</l:MenuViewModel>
</l:MainViewModel.Menus>
</l:MainViewModel>
</Window.DataContext>
</Window>
There are two main components to the above:
Data templates. These tell WPF how to map the view model data structure to elements in the UI. There are two here: a general-purpose one that just always shows, in a TextBlock element, the Name property of any view model data type; and a template that is specific to the DetailsViewModel object, and which displays just the values we're interested in as details.
The UI itself. This is super-simple: two ComboBox elements, providing the drop-down interface to select both a menu and an item on that menu; one ContentPresenter, a control whose main job is just to provide a place to render a data template for a given object; and a Grid object to organize it all. The ComboBox controls explicitly opt in to the data template that displays the item's Name property value, while the ContentPresenter infers the correct data template from the type of view model being used (but it also allows the template to be set explicitly, if you so desire).
The only other thing up there is the DataContext for the window itself, the content of which I've declared in the XAML here just because it's convenient for the purpose of the sample. In your real-world program, which appears to retrieve data from a database, you'd probably have the top-level view model handle populating itself based on that.
(Speaking of the data context: in the above, all binding paths are relative to the top-level view model. For the purpose of the sample, this is more convenient, but you of course have complete control over the data context for any element in the UI. An alternative way to implement this would be to set the DataContext properties for the controls lower in the dependency hierarchy, so that you don't have to repeat the top-level view model's property names in the binding paths.)
And that's all there is to it. You can compile and run the above code, and it will do just what you're asking for your code to do.
Minor notes:
All of the selections start out blank; you can of course initialize them to non-null values if you want, but doing so in the sample above would just add more code for no useful benefit, at least for the purpose of the sample.)
The view models here all use List<T> for their collections. This is fine for the example, because these collections never change. But as you likely already know, real-world WPF programs usually use ObservableCollection<T>, because generally they are including features that allow for those collections to be modified while the program runs. ObservableCollection<T> implements INotifyCollectionChanged, which in turn allows WPF to keep the UI in sync with the bound data. Feel free to replace List<T> with ObservableCollection<T> here or in any other similar scenario.
As promised, here's the code for the NotifyPropertyChangedBase class. There are lots of different ways to implement a base class like this, and in fact I have a different version with a couple more features that I typically use. But this one works well for a basic WPF example (indeed, for many even this one is too "feature-rich" :) ):
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

WPF, EF: Show and save ordering of rows in datagrid, mvvm

My users want to be able to re-arrange the rows in some datagrids and other list controls. There is already an existing database with a SortOrder column (integer). I use entity framework 6.1. My view needs to show the items ordered by this column, and any changes to the ordering must be saved to the database as well, when the user clicks "save" and context.SaveChanges is called
My best attempt so far is to add a SortOrder column to my datagrid and sort by it (and I intend to make it invisible somehow...), attach PreviewKeyDown events for up/down that call commands in my viewmodel, that in turn update the SortOrder values. However, even if I do RaisePropertyChanged("MyDataGridItemSource"), the datagrid is not updated, and I've tried setting mode=twoway, NotifyBySource=true, NotifyByTarget=true. Re-setting the MyDataGridItemSource completely will update the value of the SortOrder column, but it will not re-arrange the rows according to it and I'll also lose my selection which is unwanted.
Do you have any good suggestions for mapping a database sorting column to controllers like this?
The easiest way to do it is to wrap the collection in a CollectionViewSource.
First of all you need to make sure that collection items implement INotifyPropertyChanged and PropertyChanged event is raised whenever the SortOrder property changes. Then define collection view source like this:
<CollectionViewSource x:Key="CollectionView" Source="{Binding Collection}" IsLiveSortingRequested="True">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="SortOrder" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
where xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase", and put it in a DataGrid's ancestor control's resources dictionary (for example in Window.Resources or UserControl.Resources). Lastly set the defined collection view source as items source of your DataGrid:
<DataGrid ItemsSource="{Binding Source={StaticResource CollectionView}}">
...
</DataGrid>
Now whenever SortOrder property is changed on any item the UI should be updated accordingly.
UPDATE
If items do not implement INotifyPropertyChanged the above solution won't work. You may want to consider creating a wrapper class that would expose necessary properties and implement INotifyPropertyChanged (this design pattern is commonly known as "decorator pattern"). However, if it is not an option, you can define collection view on your view model and bind to it instead to the collection itself, and manually refresh the view whenever any changes are made to items. Here's an example of how it might look like:
public IEnumerable<Item> Collection
{
get { ... }
set
{
//store the value in the backing field
if (value != null)
{
CollectionView = CollectionViewSource.GetDefaultView(value);
CollectionView.SortDescriptions.Add(new SortDescription
{
Direction = ListSortDirection.Ascending,
PropertyName = "SortOrder",
});
}
else
CollectionView = null;
}
}
public ICollectionView CollectionView
{
get { ... }
set
{
//store the value in the backing field and raise PropertyChanged
}
}
In XAML, bind to the collection view:
<DataGrid ItemsSource="{Binding CollectionView}">
...
</DataGrid>
Then, whenever you make changes to items, call CollectionView.Refresh() when you're done and UI will be updated.

can't bind to IsEnabled property of ToggleButton using caliburn micro

I'm trying to bind the IsEnabled property of a ToggleButton with no success.
once the NotifyOfPropertyChange is fired, I'm getting the following exception:
Value does not fall within the expected range.
Using a simple Button, the above configurations works as expected.
I wonder if there any workaround for that one?
Thanks
UPDATE:
well it took me a while to pinpoint the problem, but finally managed to understand the behavior:
I've created a simple tester where I use a button to enable/disable a ToggleButton.
when the ToggleButton control does not contain anything, all works properly; however, after adding sub controls to it (in our case I just added a StackPanel) an exception is raised:
Value does not fall within the expected range - right after NotifyOfPropertyChange() is called.
Here is the problematic view I'm using:
<StackPanel>
<ToggleButton x:Name="SayHello" Grid.Column="1" IsEnabled="{Binding HasValue}" Height="190">
<StackPanel x:Name="sp"> </StackPanel>
</ToggleButton>
<Button x:Name="Click"></Button>
</StackPanel>
The ViewModel:
private bool _hasvalue;
public bool HasValue
{
get { return _hasvalue; }
set
{
_hasvalue = value;
NotifyOfPropertyChange(() => HasValue);
}
}
public void Click()
{
HasValue = !HasValue;
}
Any way to workaround that one? - the platforms is WP8.
I couldn't replicate the error from the example above, is there additional information in your ViewModel?
you should also be able to get the effect you want (although I'd still be interested to see the root cause of your error), by using the Caliburn.Micro conventions. Is x:Name=sp causing anything to be bound?
If you have a method SayHello, with a UI element bound to the method via a convention: x:Name="SayHello"
You can create a bool property on your ViewModel called CanSayHello, which Caliburn.Micro will use to Enable/Disable the control; although you will have to call NotifyPropertyChanged when that property changes (so the UI is aware and can update the control).
E.g.
<!-- Your existing Control, Note `IsEnabled` is not bound -->
<ToggleButton x:Name="SayHello" Height="40">
// On your ViewModel
public bool CanSayHello
{
get
{
return HasValue;
}
}
public void Click()
{
HasValue = !HasValue;
NotifyOfPropertyChange(() => CanSayHello);
}
Some additional info.

WPF binding update notification for nested property when parent property changes

I have a ViewModel with a complex property type and want to bind my view to a nested property of this object.
My ViewModel is implementing INotifyPropertyChanged (or do be extact BaseViewModel is implementing it). The class of the parent property is not implementing INotifyPropertyChanged.
The class Car is not implementing INotifyPropertyChanged. But I'm not changing the property Manufacturer, I change the MyCarProperty property, and so I expect that the OnNotifyPropertyChanged event will trigger the value update?
When I'm updating the value of the parent property, the nested property is not updating. Can you tell me how can I implement this functionality?
ViewModel
public class ViewModel : BaseViewModel
{
private Car _myCarProperty;
public Car MyCarProperty
{
get { return _myCarProperty; }
set
{
if (value == _myCarProperty) return;
_myCarProperty = value;
OnPropertyChanged();
}
}
}
Binding in the View
<TextBlock Text="{Binding Path=MyCarProperty.Manufacturer}" />
When I change the value of MyCarProperty the View does not update.
Thanks for any help!
Edit: OnPropertyChanged() implementation
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged
"The class Car is not implementing INotifyPropertyChanged. But I'm not
changing the property Manufacturer, I change the MyCarProperty
property, and so I expect that the OnNotifyPropertyChanged event will
trigger the value update?"
Nope, it won't trigger the value update a level down. Bindings don't listen to property changes for an entire path, they listen only to the object that they're bound to.
I see a couple options off the top of my head (in order of my preference when I run into this):
Bind to the car, not the sub property, and create a data template that displays what you want out of it.
Manually kick the binding by calling UpdateTarget on it's BindingExpression when you need to.
I know it looks like there's a lot more to learn on the data template route, but I promise you that data templates will prove vastly more powerful, scalable, maintainable, and useful than manually kicking bindings as you work more in WPF. (Also, once you understand them, I think they're actually less work than manually kicking bindings).
Good luck!
The accepted answer explains how to handle the case where a sub-property on a Binding Source is changed and you wish to update the view - which is not what the question is asking. WPF will in fact respond to changes from many levels down, so long as you are notifying changes for any properties being changed within the specified path.
As for this:
"The class Car is not implementing INotifyPropertyChanged. But I'm not changing the property Manufacturer, I change the MyCarProperty property, and so I expect that the OnNotifyPropertyChanged event will trigger the value update?"
WPF handles this already.
In your example, ViewModel is the Binding Source. When you set MyCarProperty (firing the NotifyPropertyChanged event), WPF will re-evaluate the Binding Target Value using the Binding Path for the new Binding Source object - updating your view with the new Manufacturer.
I have tested this with a simple WPF app - it also holds true for very deeply nested Paths:
https://pastebin.com/K2Ct4F0F
<!-- When MyViewModel notifies that "MyCarProperty" has changed, -->
<!-- this binding updates the view by traversing the given Path -->
<TextBlock Text="{Binding Path=MyCarProperty.Model.SuperNested[any][thing][you][want][to][try][and][access].Name}" />
I'm not an WPF expert, but I think it's because you've chosen the wrong path.
<TextBlock Text="{Binding Path=MyCarProperty, Value=Manufacturer}" />
update:
<TextBlock Text="{Binding Source=MyCarProperty, Path=Manufacturer}" />

XAML inline data binding doesn't work; code behind binding works

Greetings folks!
I'm running into a problem with WPF databinding that I hope you can help out with. I'm new to WPF but an expereienced developer (VB 3.0-6.0, C#).
Here's the scenario:
I have a C# project called MasterPartsData which contains a number of classes which reprsent different types of parts (capacitor, diode, etc). They inherit from a base class called clsPart.
I have another C# WPF project which contains WPF UserControls (as well as a MainWindow) to visually represent the values stored in an individual MasterPartsData (MPD) object. I've created a private field in the usercontrol to hold the object with a getter and setter.
If I create a binding explicitly in the setter for the populated object:
_capacitor = value;
Binding binding = new Binding();
binding.Source = _capacitor;
binding.Path = new PropertyPath("C0uf");
this.txtC0uf.SetBinding(TextBox.TextProperty, binding);
(with _capacitor being the private object variable and C0uf being the property name)
the value correctly displays.
However I don't wish to have to explicitly create each binding in the code behind. My preference is to create the bindings inline in XAML, perhaps with a DataContext pointing to the object.
Unfortunately every different permutation I've tried fails to work; the text box doesn't show data.
I have a couple of suspicions:
1) The binding is correct, but the text box needs to be refreshed.
2) The binding is confused between the private variable and the properties.
3) Maybe the fact that the class is defined in a different project is causing issues.
4) I'm going mad and should check myself into an asylum before someone gets hurt. :)
Any help you can provide would be most appreciated. I'm more than happy to add more information, but didn't want to clutter the question with pages and pages of source.
With respect to your suspicions:
1) I think the default binding behavior of a TextBox is TwoWay, with a LostFocus update trigger, meaning that your UI focus will have to change to another control before the binding will update, if changes are made in the UI.
If changes are made in the code you need to raise the NotifyPropertyChanged event in order for the binding system to see it.
2) This is probably not the case, but it leaves the impression that you're trying to set bindings on your UserControl properties, which is not the way data binding was designed to be used in this particular kind of use case. What you want is to bind data from non-UI classes to dependency properties on your UserControls.
3) This will never matter, as long as your UI project has a reference to your classes.
4) This is a common reaction people have when beginning to use XAML and WPF. It's like instead of being handed a box of Legos, you just got handed an injection molding machine with insufficient instructions, isn't it?
Overall, this is a situation where you might need to examine your design; elements of the "Model-View-ViewModel" pattern will come in handy. If you're unfamiliar with this, it's a development pattern in which you introduce a "ViewModel" class, perhaps you can call it MasterPartsVM which contains an implementation of INotifyPropertyChanged.
The DataContext of your UserControl would be set to this MasterPartsVM class.
A brief code example, using some generic names. Given a ViewModel class with a small backing class that looks like this:
class PartViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public PartClass Data { get; set; }
public String SomeVMProperty
{
get { return Data.SomeProperty; }
set
{
if (Data.SomeProperty != value)
Data.SomeProperty = value;
this.PropertyChanged(this, new PropertyChangedEventArgs("SomeVMProperty"));
}
}
}
class PartClass
{
public string SomeProperty { get; set; }
}
The XAML of a basic UserControl would look like this:
<UserControl x:Class="WpfApplication1.PartUserControl"
... >
<Grid>
<TextBox Text="{Binding SomeVMProperty}" Margin="68,77,104,176" />
</Grid>
</UserControl>
To connect your data class to this UserControl, you set the UserControl's DataContext property. If you do this in code, it's a matter of having a reference to your user control and the ViewModel, and then setting the property:
MyUserControlInstance.DataContext = new PartViewModel(); // or some existing PartViewModel
That combination of code should work to produce a textbox whose Text property changes every time the SomeVMProperty property is changed.
In a basic binding scenario, if your class looks like this
public class MasterPartsData
{
private string _c0uf;
public string C0uf
{
get { return _c0uf;}
set { _c0uf = value;}
}
public MasterPartsData()
{
C0uf = "Hello World!";
}
}
your XAML would look like this
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" >
<Window.DataContext>
<local:MasterPartsData />
</Window.DataContext>
<Grid>
<TextBlock Text="{Binding Path=C0uf}" />
</Grid>
</Window>
Note, there are many different approaches to setting the DataContext, you don't necessarily just have to do it in the XAML
Also, typically your MasterDataParts class would implement INotifyPropertyChanged

Categories