I'm updating some existing WPF code and my application has a number of textblocks defined like this:
<TextBlock x:Name="textBlockPropertyA"><Run Text="{Binding PropertyA}"/></TextBlock>
In this case, "PropertyA" is a property of my business class object defined like this:
public class MyBusinessObject : INotifyPropertyChanged
{
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
private string _propertyA;
public string PropertyA
{
get { return _propertyA; }
set
{
if (_propertyA == value)
{
return;
}
_propertyA = value;
OnPropertyChanged(new PropertyChangedEventArgs("PropertyA"));
}
}
// my business object also contains another object like this
public SomeOtherObject ObjectA = new SomeOtherObject();
public MyBusinessObject()
{
// constructor
}
}
Now I have a TextBlock that I need to bind to one of the properties of ObjectA which, as you can see, is an object in MyBusinessObject. In code, I'd refer to this as:
MyBusinessObject.ObjectA.PropertyNameHere
Unlike my other bindings, "PropertyNameHere" isn't a direct property of MyBusinessObject but rather a property on ObjectA. I'm not sure how to reference this in a XAML textblock binding. Can anyone tell me how I'd do this? Thanks!
Before <Run Text="{Binding ObjectA.PropertyNameHere}" /> will work you have to make ObjectA itself a property because binding will only work with properties not fields.
// my business object also contains another object like this
public SomeOtherObject ObjectA { get; set; }
public MyBusinessObject()
{
// constructor
ObjectA = new SomeOtherObject();
}
You can simply type this:
<TextBlock Text="{Binding ObjectA.PropertyNameHere"/>
You may want to implement INotifyPropertyChanged within your ObjectA class, as changing properties of the class will not be picked up by the PropertyChanged methods in your MyBusinessObject class.
Try to instantiate ObjectA in the same way as you are doing for PropertyA (Ie. as a property, with a public getter / setter, and calling OnPropertyChanged), then your XAML can be :
<TextBlock Text="{Binding ObjectA.PropertyNameHere}" />
You can do a same as you do for PropertyA like follows,
OnPropertyChanged(new PropertyChangedEventArgs("ObjectA"));
on Designer XAML,
<TextBlock x:Name="ObjectAProperty" Text="{Binding ObjectA.PropertyNameHere}" />
Try this:
In code:
public MyBusinessObject Instance { get; set; }
Instance = new MyBusinessObject();
In XAML:
<TextBlock Text="{Binding Instance.PropertyNameHere" />
Related
My ComboBox does not get populated with data.
Class Employee set to public, has variables such as:
public int EmployeeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
Code on UserControl:
public IEnumerable<csEmployee> employeeList;
public ObservableCollection<csEmployee> _employeeSorted { get; set; }
public ucAddClient()
{
InitializeComponent();
//Establish connection
var GetMyData = new DataAccess();
//Get data by procedure
employeeList = GetMyDataPV.ExecuteStoredProc<csEmployee>("procedure", new {KeyDate = Key_to_extract});
employeeList = employeeList.Where(record => record.EmployeeLevelID > 300);
_employeeSorted = new ObservableCollection<csEmployee>(employeeList.Where(record => record != null));
}
And WPF:
<ComboBox x:Name="cbAddManager"
Foreground="#FF4D648B"
FontSize="12"
IsEditable="True"
ItemsSource="{Binding _employeeSorted}"
DisplayMemberPath="FirstName"
PreviewKeyDown="cbAddManager_PreviewKeyDown"
Width="200">
<!--<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Width ="50" Text="{Binding LastName}"/>
<TextBlock Text=", "/>
<TextBlock Width ="50" Text="{Binding FirstName}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>-->
</ComboBox>
Do you have any idea, why ComboBoxis not populated? When I do this in code (I add it in user control class) it gets data needed.
Im not sure if Im binding it correctly?
That is because you assign a new instance of a collection to your _employeeSorted property after InitializeComponent. At that time, the binding is already set up and does not get notified that you have updated the property from null, because you do not implement INotifyPropertyChanged.
There are multiple ways to solve the issue:
Initialize the collection before InitializeComponent and work on this same collection if you intend to change it, using Clear and Add instead of creating a new instance on changes.
Implement the INotifyPropertyChanged interface and use it to notify changes to your property so that the bindings are updated the the changes are applied in the user interface, e.g.:
public partial class MyUserControl : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<csEmployee> _employeeSortedField;
public ObservableCollection<csEmployee> _employeeSorted
{
get => _employeeSortedField;
set
{
if (_employeeSortedField == value)
return;
_employeeSortedField = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Expose a depenedency property for the collection instead and bind it to a collection in your view model that is passed as data context of the UserControl, thus moving the data access out it and separating the view from the business logic and data (recommended, see below MVVM).
Another issue might be that you do not set your data context to the UserControl itself in XAML (which is not recommened by the way, although it might solve your issue). In this case, the binding is unable to resolve the property at runtime (a binding error will be shown in the output window).
<UserControl x:Class="YourProject.YourControl"
...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
As a note, it seems that you mix your business logic with your UserControl (view). Leverage the MVVM design pattern to create view models and seprate both concerns instead. Furthermore, if you set the data context of your UserControl to itself, you break data context inheritance.
Summary
I've got an element within a data template, that I want bound to some property of the main data context.
I realise that in this specific situation, a different solution may be preferable (and I have a working solution that avoids this), but I suspect this kind of problem may come up again and I want to know how to solve it in the general case.
Below are the specifics of my situation.
The Details
Data Hierarchy: I have a list of type A, each instance of A has a list of type B, each instance of B has some other data including a string for a text log.
UI Structure: I have a ComboBox to select an item of type A. I have a TabControl with the tabs representing items of type B, taken from the selected A above. In each tab, there is a means to enter data to populate the object of type B, and a log, representing changes to that instance of B.
Backing Logic: I track the selected item in each list with properties (SelectionA and SelectionB in the data context, MainWindowViewModel) that notify when they change. The B object also notifies when its log text changes. These ensure that the UI responds to changes to the backing data.
Problem: I want to move the notify logic to all be in one place (the DataContext, i.e. MainWindowViewModel), rather than having some in the B class and needing to duplicate the notify logic. To achieve this, I add a property (SelectionBLogText) to track the LogText property of the SelectionB object, and bind the log (in the templated tabpanel) to the main SelectionBLogText property. The problem is that within the tabpage, I can only seem to bind to properties of the selected B object (from the selected tab), and I need to bind to a property of the DataContext instead. I've tried using RelativeSource but nothing I've tried so far works, and the more I look at the docs the more I feel it's designed for another job.
The XAML (with irrelevant details removed):
<Window x:Class="WPFQuestion.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFQuestion"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="930">
<DockPanel>
<ComboBox
ItemsSource="{Binding ListOfA}"
SelectedItem="{Binding SelectionA}"
DisplayMemberPath="Name"/>
<TabControl
ItemsSource="{Binding SelectionA}"
SelectedItem="{Binding SelectionB}"
DisplayMemberPath="Name">
<TabControl.ContentTemplate>
<ItemContainerTemplate>
<StackPanel>
<TextBox
IsReadOnly="True"
Text="{Binding Path=???.SelectionBLogText}"/>
<Button Click="ClearLogButton_Click"/>
</StackPanel>
</ItemContainerTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</Window>
And the code-behind:
public partial class MainWindow : Window
{
internal MainWindowViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new MainWindowViewModel();
DataContext = vm;
}
// Various methods for event handling
}
public class A : IEnumerable<B>
{
public string Name { get; set; }
public List<B> Bs { get; set; }
}
public class B // previously : INotifyPropertyChanged
{
public string Name { get; set; }
public string LogText { get; set; }
// various other properties
}
public class MainWindowViewModel : INotifyPropertyChanged
{
private A _a;
private B _b;
public event PropertyChangedEventHandler PropertyChanged;
public List<A> ListOfA { get; set; }
public A SelectionA
{
get => _a;
set
{
if (_a == value)
{
return;
}
_a = value;
RaisePropertyChanged(nameof(SelectionA));
}
}
public B SelectionB
{
get => _b;
set
{
if (_b == value)
{
return;
}
_b = value;
RaisePropertyChanged(nameof(SelectionB));
RaisePropertyChanged(nameof(SelectionBLogText));
}
}
public string SelectionBLogText
{
get => SelectionB.LogText;
set
{
if (SelectionB.LogText == value)
{
return;
}
SelectionB.LogText = value;
RaisePropertyChanged(nameof(SelectionBLogText));
}
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
have you tried something like this when you used relative binding? if not please check this out.
<TextBox IsReadOnly="True"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window},
Path=Datacontext.SelectionBLogText}"/>
I have a textbox binded to one of my viewModel's properties
<TextBox x:Name="box" Height="20" TextWrapping="Wrap" Text="{Binding name}"/>
viewModel.cs:
public string name { get; set; }
[...]
public void clear(){
name = "";
}
AddCommand: Icommand class:
public void Execute(object parameter){
//do some stuff
viewModel.clear();
}
Everything else works perfect. I can read the textbox's and use them to do some calculation in viewModel then bind those calculation to labels to display. But I just cant clear those textbox after I read them. I tried setting the binding to mode=twoway but still doesn't work
You need to tell WPF that the property has changed.
something similar to
private string _name;
public string name
{
get
{
return _name;
}
set
{
_name = value;
PropertyChanged(this, new PropertyChangedEventArgs("name"));
}
}
ofcourse most people would make a base class to avoid having to call that property changed method with so that complicated parameter.
Qeustion:
Is it possible to add an additional PropertyDescriptor to the PropertyDescriptorCollection of an ICustomTypeDescriptor after DataConext has been set?
Details:
I am creating a new class which implements ICustomTypeDescriptor with the goal of dynamically adding new Properties to this class at runtime, and binding them to a WPF application.
For example, a user may define a new property through a scripting language (eg. Lua) while the app is running, and I would like XAML to be able to Bind to that Property.
The issue I am running into is that if I add a property AFTER the DataContext has been set, the Binding to XAML does not work; it does not attempt to call associated PropertyDescriptor.GetValue method.
I believe this issue is that shortly after setting the DataContext to my ICustomTypeDescriptor, its implementation of ICustomTypeDescriptor.GetProperties is called, at which point I create PropertyDescriptor for all known Properties. I then later add new Properties, but don't have the ability to add new PropertyDescriptor for them.
Code:
I have trimmed down the code a bit here to make it easier to read, removing a lot of the boiler-plate stuff.
The idea in this example is that the first text field is Bound to a dynamically created property, which is defined during construction, and the second text field is Bound to a property that won't exist until the Button is clicked.
The property created when the button is clicked only gets DataBound properly if I set the DataContext to something new.
public class MyCustomType : ICustomTypeDescriptor, INotifyPropertyChanged
{
Dictionary<string, object> Properties = new Dictionary<string, object>();
public event PropertyChangedEventHandler PropertyChanged;
public MyCustomType()
{
Properties.Add("CustomProp", "What up, world?");
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public void CreateOrSetProperty(string name, object val)
{
Properties[name] = val;
NotifyPropertyChanged(name);
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
List<PropertyDescriptor> props = new List<PropertyDescriptor>();
foreach (KeyValuePair<string, object> entry in Properties)
{
props.Add(new MyCustomTypePropertyDescriptor(entry.Key, attributes));
}
return new PropertyDescriptorCollection(props.ToArray());
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(null);
}
class MyCustomTypePropertyDescriptor : PropertyDescriptor
{
public MyCustomTypePropertyDescriptor(string name, Attribute[] attrs)
: base(name, attrs)
{
}
public override object GetValue(object component)
{
return ((MyCustomType)component).Properties[Name];
}
public override bool IsReadOnly
{
get { return false; }
}
public override void SetValue(object component, object value)
{
((MyCustomType)component).Properties[Name] = value;
}
}
}
public partial class Page5
{
public Page5()
{
this.InitializeComponent();
}
private void BtnAddProperty_Click(object sender, System.Windows.RoutedEventArgs e)
{
// A static resource defined in XAML used as the DataContext for both text blocks.
MyCustomType c = Resources["MyCustomTypeData"] as MyCustomType;
// Create a new property which is already being referenced in a DataBind in XAML but
// prior to this function being called, did not exist.
c.CreateOrSetProperty("AnotherProp", "Another Property!");
// If I clear the DataContext and set it back to the previous value it triggers
// ICustomTypeDescriptor.GetProperties to get called again, and the property gets
// Bound. If I don't do this, it fails.
//MyTextBlock2.DataContext = null;
//MyTextBlock2.DataContext = c;
}
}
<Page.Resources>
<WpfApplication1:MyCustomType x:Key="MyCustomTypeData"/>
</Page.Resources>
<Grid x:Name="LayoutRoot">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
<TextBlock DataContext="{StaticResource MyCustomTypeData}" x:Name="MyTextBlock" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding CustomProp}" VerticalAlignment="Top" FontSize="32" Background="Black" Foreground="White"/>
<TextBlock DataContext="{StaticResource MyCustomTypeData}" x:Name="MyTextBlock2" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding AnotherProp}" VerticalAlignment="Top" FontSize="32" Background="Black" Foreground="White"/>
<Button x:Name="BtnAddProperty" Content="Add Additional Property" Click="BtnAddProperty_Click"/>
</StackPanel>
</Grid>
I don't fully understand your question. However, I guess it maybe part of your problem that you use a Dictionary in a binding expression. In my opinion the collection which will be bound in xaml should be a type derrived from ObservableCollection in order to own the ability to notify view when collection change.
I need to bind a property to a label. i have written the following code:
xaml for the label is
<Label Canvas.Left="807.3" Canvas.Top="148.9" Height="33.567" x:Name="label2"
Width="98" FontFamily="Tw Cen MT" FontSize="24" FontWeight="Bold"
Foreground="#FFFEE3A4"
Content="{Binding Path=UserInformation.AccountBalance,Mode=OneWay}">
<Label.Background>
<ImageBrush />
</Label.Background>
</Label>
The class whcih have the AccountBalance
public class CustomerInformation : INotifyPropertyChanged
{
public CustomerInformation()
{
_Balance = 0.0;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public double AccountBalance
{
get { return _Balance; }
set
{
_wepaBalance = value;
FirePropertyChanged("AccountBalance");
}
}
protected void FirePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
datacontext is set as below
this.LayoutRoot.DataContext = this;
behind the xaml.cs the following code is written to access the UserInfo which is a global object
public CustomerInformation UserInformation
{
get
{
return Globalobjs._Object.UserInfo;
}
}
xamls.cs is derived from Window only.
The problem is PropertyChangedEventHandler of INotifyPropertyChanged is always null when called .
Can any 1 please help me on this issue?
this.LayoutRoot.DataContext = this;
This is the Window, yet you are setting the Window instance as the DataContext. Set the DataContext to the UserInformation.
this.LayoutRoot.DataContext = Globalobjs._Object.UserInfo;
Does the datacontext that you are binding to implement INotifyPropertyChanged?
If this is not an MVVM patterned project, ensure that the class that contains the property that you are binding to implements that interface, and be sure to call the delegate for the event when you change the property (e.g. OnPropertyChanged("MyProperty"))
If it is an MVVM project and you are not using a framework, it is best to derive all of your ViewModels from a ViewModel base that implements INotifyPropertyChanged.
You are binding to the Windows's DataContext. But the Windows DataContext is not the same as the Windows's code behind, where you have UserInformation property defined. To access a property defined in your Window's code behind, you have to set your Window's Name property, then use the following binding instead:
Content="{Binding ElementName=YourWindowName, Path=UserInformation.AccountBalance,Mode=OneWay}"