WPF Binding Via StaticResouces - c#

Given the following Xaml:
<Window.Resources>
<System:String x:Key="StringValue"></System:String>
</Window.Resources>
<Grid>
<ComboBox Margin="137,101,169,183" ItemsSource="{Binding collection}" SnapsToDevicePixels="True" IsHitTestVisible="true">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Command="{Binding CheckCommand}" IsChecked="{Binding IsChecked}" Content="{Binding Name}"/>
<TextBlock Text="{StaticResource StringValue}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
What I want is for the Textblock Text to be bound to a static resource, that is databound to a value on the ViewModel. The issue is System.String appears to not allow databinding. ANybody know of a way to do this? For context, the TextBlock needs a different itemssource than that of its parent combobox.
Thanks.

String doesnt allow binding because it is not a DependencyObject (and doesnt implement INotifyPropertyChanged)
but why dont you just bind directly to the Value in the ViewModel?
if you cannot bind to a ViewModel (think about RelativeSource with searching Parent type) you can implement a wrapper (which implements INotifyPropertyChanged to get the changes in the object)
Example wrapper class:
public class BindWrapper<T> : INotifyPropertyChanged
{
private T _Content;
public T Content
{
get
{
return _Content;
}
set
{
_Content = value;
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
how to instantiate and bind in XAML:
<Window.Resources>
<local:BindWrapper x:Key="wrapper" x:TypeArguments="System:String">
<local:BindWrapper.Content>
<System:String>huuu</System:String>
</local:BindWrapper.Content>
</local:BindWrapper>
</Window.Resources>
<TextBlock Text="{Binding Source={StaticResource wrapper}, Path=Content}" />

To clarify, A System.String has no dependency properties so you can't bind it anything. I think you need a convertor so your TextBlock can bind to the View Model. What type of ObservableCollection do you have on the View Model?
EDIT If you just want to bind a simple string to the text property this is the wrong answer. If you want to bind to formatted text, read on.
I was having this problem before. I wanted to bind my TextBlock to a string resource in my properties. I ended up subclassing TextBlock to BindableTextBlock and making and a Convertor for string to an Inline list.
Question and Answers here.
It may seem a little involved, there ought to be an easier way. However I've resused the control several times whenever I've needed to bind to some formatted text and it works. Hopefully you can benefit from my work, and perhaps improve.

Related

UWP x:bind issue - Invalid binding path 'dpl' : Property 'dpl' not found on type 'DataTemplate'

I have a class PricingData and PricingSchedule. Where PricingSchedule is a List<> inside PricingData class. I want to bind data of this class to UWP controls.
Sample code is available to download here : https://github.com/jigneshdesai/SampleOfBindingIssue1.git
How Code looks: i have a start page(mainpage) that hosts ListView control, Listview has PricingUserControl within it. PricingUserControl looks like this
<TextBlock x:Name="lblPriceHeader" Text="{Binding PricingTitle}" Margin="0,0,50,0" />
<ComboBox x:Name="cbPriceValueList" ItemsSource="{x:Bind dpl}" DisplayMemberPath="PriceValue" SelectedValuePath="PriceValue" SelectedValue="{Binding DisplayPricing}" />
<ListView x:Name="lbPriceChangeSchedule" ItemsSource="{Binding PricingScheduleList}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="cbSchedulePriceValueList" ItemsSource="{x:Bind dpl}" DisplayMemberPath="PriceValue" SelectedValuePath="PriceValue" />
<TextBlock Text="{Binding SchedulePricingTimeZone }" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
What i want to achieve: Combobox should populate a list of values (eg. 1USD, 2USD, 3USD etc.). Then when you provide List of records from database, the listbox will repeat PricingUserControl and combobox within it should set its value property (SelectedValue) as per record.
Issue:
ComboBox x:Name="cbPriceValueList" uses x:bind dpl where dpl is a local variable of PricingUserControl. It populates the list properly. The trouble is ComboBox x:Name="cbSchedulePriceValueList" it also has x:bind dpl but during compilation it display error "Invalid binding path 'dpl' : Property 'dpl' not found on type 'DataTemplate'."
I am wondering why x:bind dpl does not work at this point. ?
I have now realized that your problem is in fact that you need to reach to a Page property from within the DataTemplate, so here is a updated answer.
You cannot use x:Bind if you need to access an outside element's property from within a DataTemplate. Instead, you can use classic {Binding} expression. First add a name to your page:
<Page
...
x:Name="Page">
And now refer to this name from within the DataTemplate:
<ComboBox
x:Name="cbSchedulePriceValueList"
ItemsSource="{Binding ElementName=Page, Path=dpl}"
DisplayMemberPath="PriceValue"
SelectedValuePath="PriceValue" />
Original answer
To be able to use x:Bind inside of a DataTemplate, you must specify the data type the individual items of the control will have, using x:DataType. Suppose your PricingScheduleList is a List<MyApp.Models.MyType>, then you will first need to add this XML namespace to the <Page> element:
xmlns:models="using:MyApp.Models"
And then set the x:DataType attribute as follows:
<DataTemplate x:DataType="models:MyType">
...
</DataTemplate>
You can confirm this works by the fact that IntelliSense should now suggest you the properties of MyType when you start writing the x:Bind expression.
By checking your code, the reason why SelectedValue does not take effect is when you choose the item from ComboBox, you didn't notify your DisplayPricing to change. So you need to implement INotifyPropertyChanged interface in your PricingData. Do the same behavior in PricingSchedule.
public class PricingData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
......
public string DisplayPricing
{
get => $"{PricingValue} {PricingCurrency}";
set
{
var sp = value.Split(' ');
PricingValue = sp.First();
PricingCurrency = sp.Last();
OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
PScheduleUserControl.xaml:
<ComboBox x:Name="cbPriceValueList" ItemsSource="{x:Bind myList}" DisplayMemberPath="PriceValue" SelectedValuePath="PriceValue" SelectedValue="{Binding DisplayPricing,Mode=TwoWay}" />

Wpf call function of ContentControl view

I am pretty new to C# so I am just starting to learn the basics. Right now I have a ContentControl inside a Window like this:
<ContentControl Content="{Binding}" x:Name="SubView"/>
And I configured my resources of the Windows like this:
<Window.Resources>
<DataTemplate x:Name="StammdatenViewTemplate" DataType="{x:Type viewmodels:StammdatenViewModel}">
<views:StammdatenView DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Name="AdministrationViewTemplate" DataType="{x:Type viewmodels:AdministrationViewModel}">
<views:AdministrationView DataContext="{Binding}" />
</DataTemplate>
</Window.Resources>
In my Window class I am setting the DataContext like this:
DataContext = new StammdatenViewModel();
Here is the thing I would like to do. I want to disable all the TextBoxes inside the ContentControl. I thought about adding a function to my StammdatenView.xaml.cs class (which is the class of my subview), then firing the event from the Window somehow. Though I would need access to the function inside the subview. Is that somehow possible and if yes how? Or would anyone suggest a different approach?
Thanks in advance.
Or would anyone suggest a different approach?
Yes. You should bind the IsEnabled property of each TextBox in the StammdatenView to a boolean property of the StammdatenViewModel.
You can then disable the TextBoxes by setting the source property in the view model class. This is one of the key aspects of the MVVM design pattern, i.e. that you handle your application logic in the view model.
Make sure that the view model class implements the INotifyPropertyChanged interface and provide change notifications as explained on MSDN.
You can create a INotifyPropertyChanged event on your view model, then bind it to the 'IsEnabled={Binding IsTextBoxEnabled}' attribute in your view template for the textbox.
public class ViewModel : BaseViewModel
{
private bool _isTextBoxEnabled;
public bool IsTextBoxEnabled
{
get { return _isTextBoxEnabled; }
set
{
if (value != _isTextBoxEnabled)
_isTextBoxEnabled = value;
this.RaisePropertyChanged("IsTextBoxEnabled");
}
}
}
XAML
<DataTemplate x:Key="template">
<StackPanel Orientation="Horizontal" DataContext="{Binding}">
<TextBox IsEnabled="{Binding IsTextBoxEnabled}" />
</StackPanel>
</DataTemplate>

Best way to bind a set of same-type ViewModels to a TabControl in MVVM / WPF

I have an existing ViewModel and View in an MVVM project. Effectively this View presents a collection of items in a particular, styled way. I'll call this existing ViewModel "CollectionPresenter".
Up to now, this has been presented as as follows in XAML:
<Grid>
<ns:CollectionPresenter />
</Grid>
Now, I want to have a dynamic collection of these "CollectionPresenter" view models made available ideally in a tab view.
My approach has been to define an observable collection of these "CollectionPresenters", creating them first on construction of the parent view model. The XAML above then changed to look something like this:
<TabControl ItemsSource="{TemplateBinding CollectionPresenters}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding CollectionPresenterTitle}">
</DataTemplate>
<TabControl.ItemTemplate>
<TabControl.ContentTemplate>
... this is where things get confusing
</TabControl.ContentTemplate>
<TabControl>
You can see above my problem is the ContentTemplate.
When I load this up, I get a tab control and it has as many tabs as my observable collection of "CollectionPresenter" objects.
However, the content of the tab control is always empty.
Is this approach correct - and is there a better way regardless?
EDIT: ADDING SOME EXTRA THINGS TO MAKE IT CLEARER
I've tried the below, but it doesn't work. The XAML with the Tab Control (the binding to "Things" works fine):
<TabControl ItemsSource="{TemplateBinding Things}">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModels:Thing}">
<TextBlock Text="{Binding ThingName}" Width="200" Background="Blue" Foreground="White"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type viewModels:Thing}">
<TextBlock Text="{Binding ThingName}" Width="500" Height="500" Background="Blue" Foreground="White"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
The definition for the "Things" observable collection (which is inside the templated parent (ParentObject) of the XAML with the tab control):
public static readonly DependencyProperty ThingsProperty =
DependencyProperty.Register("Things", typeof(ObservableCollection<Thing>), typeof(ParentObject), new PropertyMetadata(null));
public ObservableCollection<Thing> Things
{
get { return (ObservableCollection<Thing>)GetValue(ThingsProperty); }
set { SetValue(ThingsProperty, value); }
}
Stripped down version of the "Thing" view model:
public class Thing : ViewModelBase
{
public Thing()
{
}
public void Initialise(ObservableCollection<Thing> things, string thingName)
{
Things = things;
ThingName = thingName;
}
public static readonly DependencyProperty ThingNameProperty =
DependencyProperty.Register("ThingName", typeof(string), typeof(Thing), new PropertyMetadata(null));
public string ThingName
{
get { return (string)GetValue(ThingNameProperty); }
set { SetValue(ThingNameProperty, value); }
}
}
Looking at my answer to the WPF MVVM navigate views question, you can see this:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:PersonViewModel}">
<Views:PersonView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:CompanyViewModel}">
<Views:CompanyView />
</DataTemplate>
Now, wherever we use an instance from one of these types in our application, these DataTemplates will tell the framework to display the related view instead.
Therefore, your solution is to simply not hard-code one single DataTemplate to the TabControl.ItemTemplate property, but to leave that blank instead. If you use multiple DataTemplates without providing x:Key values, then they will implicitly be applied when each data object is to be rendered in the TabControl.
UPDATE >>>
Using these DataTemplates should leave your TabControl looking like this:
<TabControl ItemsSource="{TemplateBinding Things}" />
I'm not sure why you're using a TemplateBinding there though as you don't need to define any new templates to get this working... therefore, you should be using a plain old Binding instead.
One other thing that you need to do is to use different data types for each item in the collection that you want to display differently. You could derive custom classes from your Thing class and so the collection could still be of type ObservableCollection<Thing>.

INotifyPropertyChanged not working with WPF data binding

I have a combobox in my MainWindow.xaml file like so:
<ComboBox Name="material1ComboBox"
HorizontalAlignment="Center"
MinWidth="100"
ItemsSource="{Binding ViewProperties.MaterialDropDownValues}"
SelectedValue="{Binding ViewProperties.Material1SelectedValue}"
SelectionChanged="Material1ComboBoxSelectionChanged">
</ComboBox>
I've assigned the datacontext in the codebehind using this.datacontext = this.
I created a ViewProperties that is accessed as a property in the MainWindow and is a class that implements INotifyPropertyChanged and contains the MaterialDropDownValues as a property.
I even changed the the MaterialDropDownValues to be an ObservableCollection.
The problem is that the databinding works on initialisation however if the MaterialDropDownValues property is changed the combobox values are not updated.
I have the following in the ViewProperties class:
public ObservableCollection<string> MaterialDropDownValues
{
get { return this.materialDropDownValues; }
set
{
this.materialDropDownValues = value;
OnPropertyChanged("MaterialDropDownValues");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
Any ideas why this is not working? All the other answers I could find advised to implement INotifyPropertyChanged and make the property an observablecollection.
Solution 1:
Dont recreate this.materialDropDownValues try to do
this.materialDropDownValues.Clear();
foreach(var mystring in myStrings)
this.materialDropDownValues.Add(mystring);
for all new items. If this doesnt work then try solution 2...
Solution 2:
As per my experience, I think ObservableCollection of primitive types like int, string, bool, double etc. does not refresh on Property Change notification if ItemsControl.ItemTemplate is not specified.
<ComboBox Name="material1ComboBox"
HorizontalAlignment="Center"
MinWidth="100"
ItemsSource="{Binding ViewProperties.MaterialDropDownValues}"
SelectedValue="{Binding ViewProperties.Material1SelectedValue}"
SelectionChanged="Material1ComboBoxSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type System:String}">
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This is because the itemscontrol's items container creates non-observable item containers in it for primitive data by simply copying item.ToString(). In the code above the {Binding} should update the data changes when the whole items source is changed.
Let me know if this works.
When I bump into things like this, the first thing I do is play around with the binding mode. Something like:
ItemsSource="{Binding Path=ViewProperties.MaterialDropDownValues, Mode=TwoWay}"
That sometimes trips you up. The other thing I would make sure of is that if you're instantiating new ViewProperties object(s) following your initial load, you notify change on that. If you don't, the XAML will be referring to an outdated version of the object while your code behind/view model is operating on a different one.
Edit in response to comments
None of the below solved the problem, but is left as a reference.
Original Answer
The problem is that you have not specified the DataContext for your view, which is where WPF looks for Binding values by default.
Provided that your ViewProperties property on MainWindow is public you can simply change your binding to:
ItemsSource="{Binding ViewProperties.MaterialDropDownValues,
RelativeSource={RelativeSource FindAncestor, AncestorType=Window}"
This causes WPF to look for the property value on the first occurence of Window that it finds above the combobox in the visual tree.
Alternatively, you can just set the Window.DataContext property to your instance of ViewProperties and change the binding to the following:
ItemsSource="{Binding MaterialDropDownValues}"
Either of these will work, but I would suggest using the latter as it is closer to the MVVM pattern that is generally preferred in WPF/XAML applications.
what happens if you change your xaml to
<ComboBox Name="material1ComboBox"
HorizontalAlignment="Center"
MinWidth="100"
DataContext="{Binding ViewProperties}"
ItemsSource="{Binding MaterialDropDownValues}"
SelectedValue="{Binding Material1SelectedValue}"
SelectionChanged="Material1ComboBoxSelectionChanged">
</ComboBox>
nevertheless you should just instantiate your collection once and just use remove, add and clear when you use a OberservableCollection.
Posting this in case anyone else runs into this. I came up this as the best search result matching my symptoms, but it turns our that none of the answers worked above for me.
I was using WinUI3 and apparently it uses the newer x:Bind syntax for it's XAML. Apparently x:Bind defaults it's Mode to OneTime which is why it wouldn't update after the first value (I also tried Binding but couldn't get that to work)
From: <TextBlock Text="{x:Bind MyField}" x:Phase="1" Margin="0,5,0,5"/>
To: <TextBlock Text="{x:Bind MyField, Mode=OneWay}" x:Phase="1" Margin="0,5,0,5"/>
So if you are using x:Bind, make sure set Mode=OneWay AND implement INotifyPropertyChanged and then things should work

Data binding to a UserControl in WPF

I have a UserControl that I want to participate in data binding. I've set up the dependency properties in the user control, but can't get it work.
The uc displays the correct text when I call it with static text (e.g BlueText="ABC") . When i try to bind it to a local public property, it is always blank.
<src:BlueTextBox BlueText="Feeling blue" /> <!--OK-->
<src:BlueTextBox BlueText="{Binding Path=MyString}" /> <!--UserControl always BLANK!-->
<TextBox Text="{Binding Path=MyString}" Width="100"/> <!--Simple TextBox Binds OK-->
I've boiled the code down to the following simplified example. Here is the XAML of the UserControl:
<UserControl x:Class="Binding2.BlueTextBox" ...
<Grid>
<TextBox x:Name="myTextBox" Text="{Binding BlueText}" Foreground="Blue" Width="100" Height="26" />
</Grid>
Here is the code behind of the UserControl:
public partial class BlueTextBox : UserControl
{
public BlueTextBox()
{
InitializeComponent();
DataContext = this; // shouldn't do this - see solution
}
public static readonly DependencyProperty BlueTextProperty =
DependencyProperty.Register("BlueText", typeof(string), typeof(BlueTextBox));
public string BlueText
{
get { return GetValue(BlueTextProperty).ToString(); }
set { SetValue( BlueTextProperty, value.ToString() ); }
}
This seems like it should be really easy, but I can't make it work. Thanks for your help!
More info: When i was trying the fix suggested by Eugene, I noticed some peculiar behavior. I added a PropertyChangedCallback to the metadata; this allows me to watch the value of BlueText getting set. When setting the string to a static value (="feeling blue") the PropertyChanged event fires. The data binding case does not fire PropertyChanged. I think this means the data-bound value is not getting sent to the UserControl. (I think the constructor does not get called in the static case)
Solution: The problems were correctly identified by Arcturus and jpsstavares. First, I was overwriting the data binding when is set DataContext=this in the constructor of the control. This prevented the data bound value from getting set. I also had to name the control x:Name=root, and specify the Binding ElementName=root int the XAML. To get the TwoWay binding, I needed to set Mode=TwoWay in the caller. Here is the correct code:
<src:BlueTextBox BlueText="{Binding Path=MyString, Mode=TwoWay}}" /> <!--OK-->
Now the XAML in the UserControl:
<UserControl x:Class="Binding2.BlueTextBox" x:Name="root"...
<Grid>
<TextBox x:Name="myTextBox" Text="{Binding ElementName=root, Path=BlueText}" Foreground="Blue" Width="100" Height="26" />
</Grid>
Finally I removed the DataContext=this in the constructor of the UserControl.
public BlueTextBox()
{
InitializeComponent();
//DataContext = this; -- don't do this
}
Thanks everyone for the tremendous help!
You set the DataContext in the Control to itself, thus overwriting the DataContext when using this Control in other controls. Taking your binding as example in your situation:
<src:BlueTextBox BlueText="{Binding Path=MyString}" />
Once loaded and all the Datacontext is set, it will look for the path MyString in your BlueTextBox thing control due to you setting the DataContext to it. I guess this is not how you intended this to work ;).
Solution:
Change the text binding either one of the 2 bindings:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type src:BlueTextBox}}, Path=BlueText}
or
Name your control Root (or something like that)
<UserControl x:Name="Root"
{Binding ElementName=Root, Path=BlueText}
And remove the
DataContext = this;
from the constructor of your UserControl and it should work like a charm..
I think in this case you need to set the ElementName property in the binding. Something like this:
<UserControl x:Class="Binding2.BlueTextBox" x:Name="blueTextBox"...
<Grid>
<TextBox x:Name="myTextBox" Text="{Binding ElementName=blueTextBox, Path=BlueText}" Foreground="Blue" Width="100" Height="26" />
</Grid>
Possibly you need to add to your property FrameworkPropertyMetadata where specify FrameworkPropertyMetadataOptions.AffectsRender and AffectsMeasure.
FrameworkPropertyMetadataOptions enumeration MSDN article
I know this is an old topic but still.
Also mention the PropertyChangedCallback on the UIPropertyMetadata during registering your DP

Categories

Resources