How to use displaymemberpath on a combobox in UWP C++/CX? - xaml

So I'm trying to bind a combobox to a vector of myclass. myclass has a public Platform::String^ property called key. So in my XAML, I have displaymemberpath for the combobox set to "key". However, I just get a box of empty options, which is the same result as if i set displaymemberpath to "asdf" (a non-existent property). To illustrate my problem, I've tried to rewrite this tutorial on using displaymemberpath in C++/CX: https://asp-net-example.blogspot.com/2017/01/uwp-combobox-displaymemberpath-and.html (I've tried following the original tutorial in C# and it worked perfectly fine).
Here's my code:
(MainPage.xaml is exactly the same as the tutorial).
<Page
x:Class="displaymembercxxtest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:displaymembercxxtest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel
x:Name="stack_panel1"
Orientation="Horizontal"
Background="LightYellow"
Padding="100"
>
<ComboBox
x:Name="ComboBox1"
Header="Select A Color"
DisplayMemberPath="ColorName"
SelectedValuePath="ColorValue"
SelectionChanged="ComboBox1_SelectionChanged"
>
</ComboBox>
<TextBlock
x:Name="TextBlock1"
FontFamily="Consolas"
FontSize="25"
Foreground="DarkGreen"
Margin="50,5,5,5"
/>
</StackPanel>
</Page>
Code Behind:
//MainPage.xaml.h
namespace displaymembercxxtest
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public ref class MainPage sealed
{
public:
MainPage();
private:
void ComboBox1_SelectionChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e);
};
public ref class color sealed
{
public:
property Platform::String^ ColorName;
property Platform::String^ ColorValue;
color(Platform::String^ name, Platform::String^ value)
{
ColorName = name;
ColorValue = value;
}
};
}
   
//MainPage.xaml.cpp
MainPage::MainPage()
{
InitializeComponent();
Platform::Collections::Vector<color^>^ colors = ref new Platform::Collections::Vector<color^> ();
colors->Append(ref new color("INDIANRED", "#CD5C5C"));
colors->Append(ref new color("SALMON", "#FA8072"));
colors->Append(ref new color("CRIMSON", "#DC143C"));
colors->Append(ref new color("DEEPPINK", "#FF1493"));
colors->Append(ref new color("CORAL", "#FF7F50"));
colors->Append(ref new color("ORANGE", "#FFA500"));
colors->Append(ref new color("YELLOW", "#FFFF00"));
colors->Append(ref new color("PEACHPUFF", "#FFDAB9"));
ComboBox1->ItemsSource = colors;
}
void displaymembercxxtest::MainPage::ComboBox1_SelectionChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e)
{
// not relevant to the problem
}
Here is what I'm seeing:
Here is what it looks like when I built the tutorial in C#:
As you can see, the options are blank in the C++/CX version. What am I doing wrong?

If you're using {Binding}, you'll need to add the BindableAttribute attribute to the color class.
Please check the following code:
[Windows::UI::Xaml::Data::Bindable]
public ref class color sealed
{
public:
property Platform::String^ ColorName;
property Platform::String^ ColorValue;
color(Platform::String^ name, Platform::String^ value)
{
ColorName = name;
ColorValue = value;
}
};
More information, please read Data binding overview documentation.

Related

How can I Set Xaml code behind property from it's own XAML. e.g "this"

I have my Xamarin forms MasterDetailPage Xaml code-behind .xaml.cs file where I created a string property.
I want to set that property from the front-end Xaml.
I know it seems like a newbe question, but for crying-out-loud I can't get anything to work.
Does anyone know how to do this?
E.g.
this.stringProperty = "string" IN XAML markup.
Or pseudo-code
{RelativeSource Self}.stringProperty ="string"
Thanks
#TRS
Let me clarify by using your code. I only need the first part of your code.
public partial class MainWindow : Window <-- YOUR CODE
{
private testString; <-- ADDED THIS
public MainWindow()
{
InitializeComponent();
DataContext = new MyClass(); <-- REMOVED THIS
public string TestString; <-- ADDED THIS
{
get => testString;
set
{
testString; = value;
}
}
}
Now I want to set the "TestString" property from this same partial class's MainWindow XAML.
<Window x:Class="WpfApp6.MainWindow"
x:Name="Mywindow"
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:WpfApp6"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
SET TestString PROPERTY IN THE CODE BEHIND HERE IN THE XAML
So, with that in mind. How can I do it?
Here is minimal working example for you .But you have to read about DataContext, INotifyPropertyChanged and MVVM to understand it thoroughly,otherwise you will have many questions.Keep you code behind clean.
<Window x:Class="WpfApp6.MainWindow"
x:Name="Mywindow"
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:WpfApp6"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock Text="{Binding MyString}"></TextBlock>
</Grid>
and code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyClass();
}
}
public class MyClass : INotifyPropertyChanged
{
private string _myString;
public string MyString
{
get => _myString;
set
{
_myString = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

How to make GradientStopCollection Observable?

i have run into a problem where i want to show a list of gradient stops in a listbox. The problem is that putting the gradientstops in a collection of type ObservableCollection works, but using a GradientStopCollection does not.
When i Use GradientStopCollection, the items that are in the list before the window is initialized are shown, but when a button is pressed to add a third item, the UI is not updated.
Calling OnPropertyChanged does not result in the UI being updated. I have made a small example to try to reproduce the problem.
So how can get the window to correctly update even when i use a gradientstop collection?
using System.Windows;
using System.Windows.Media;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new ViewModel();
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ViewModel vm = (DataContext as ViewModel);
vm.Collection.Add(new GradientStop(Colors.Red, 0.5));
//This line has no effect:
vm.OnPropertyChanged("Collection");
}
}
}
Viewmodel:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
namespace WpfApp1
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public GradientStopCollection Collection
{
get
{
return collection;
}
set
{
collection = value;
}
}
//Replacing GradientStopCollection
// with ObservableCollection<GradientStop> makes it work
GradientStopCollection collection;
public ViewModel()
{
GradientStop a = new GradientStop(Colors.Green, 0);
GradientStop b = new GradientStop(Colors.Yellow, 1.0);
collection = new GradientStopCollection() { a, b } ;
OnPropertyChanged("Collection");
}
public void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class Converter : IValueConverter
{
public object Convert(object value, Type targettype, object parameter, CultureInfo cultureInfo)
{
if (value is Color color)
return new SolidColorBrush(color);
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targettype, object parameter, CultureInfo cultureInfo)
{
throw new NotImplementedException();
}
}
}
And finally the xaml:
<Grid>
<Grid.Resources>
<local:Converter x:Key="ColorConverter"/>
<DataTemplate DataType="{x:Type GradientStop}">
<TextBlock
Width="50"
Background="{Binding Color, Converter={StaticResource ColorConverter}}"
Text="block"
/>
</DataTemplate>
</Grid.Resources>
<ListBox
x:Name="GradientListBox"
Width="72"
Height="92"
ItemsSource="{Binding Collection}" />
<Button Content="Button" HorizontalAlignment="Left" Margin="169,264,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
I don't think that there is any easy way around this problem.
You could create your own collection class, inheriting from GradientStopCollection and implementing the interface INotifyCollectionChanged, effectively making an ObservableGradientStopCollection.
You can probably find an implementation of INotifyCollectionChanged as an excmple.
It might be easier, just to keep two collections, although it seems like bad style.

Binding fires on unloaded view/view-model when creating a new view

If I bind a RadioButton to a view-model property using a type converter, every time I create a view, the setter on the previous ViewModel gets called, even though the view is Unloaded and should not exist anymore. Here is the minimum code to reproduce the issue:
1) Define an enum type:
enum EnumType {
Value1,
Value2,
}
2) Define a convereter:
public class EnumTypeToBooleanConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, string language) {
return true;
}
public object ConvertBack(object value, Type targetType, object parameter, string language) {
return EnumType.Value1;
}
}
3) Define a view-model:
class ViewModel : INotifyPropertyChanged {
private EnumType value;
public ViewModel() {
Debug.WriteLine(string.Format("ViewModel ({0})::ctor", this.GetHashCode()));
}
public EnumType Value {
get {
Debug.WriteLine(string.Format("ViewModel ({0})::Value::get", this.GetHashCode()));
return this.value;
}
set {
Debug.WriteLine(string.Format("ViewModel ({0})::Value::set", this.GetHashCode()));
if (this.value != value) {
this.value = value;
this.OnPropertyChanged();
}
}
}
private void OnPropertyChanged([CallerMemberName] string name = null) {
if (this.PropertyChanged != null) {
var ea = new PropertyChangedEventArgs(name);
this.PropertyChanged(this, ea);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
4) Define a UserControl (View.xaml)
<UserControl
x:Class="BindingIssue.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BindingIssue"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
x:Name="root">
<UserControl.DataContext>
<local:ViewModel x:Name="ViewModel"/>
</UserControl.DataContext>
<Grid>
<ScrollViewer>
<StackPanel>
<RadioButton x:Name="rdo1"
Content="Value1"
IsChecked="{Binding Path=Value, Converter={StaticResource EnumTypeToBooleanConverter}, ConverterParameter=Value1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button x:Name="btnClose"
Click="btnClose_Click"
Content="Close"/>
</StackPanel>
</ScrollViewer>
</Grid>
5) Add code behind of the View:
public View() {
Debug.WriteLine(string.Format("View ({0})::ctor", this.GetHashCode()));
this.InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnUnloaded;
}
private void btnClose_Click(object sender, RoutedEventArgs e) {
if (this.Parent is Popup) {
Debug.WriteLine("Closing the popup...");
((Popup)this.Parent).IsOpen = false;
}
}
private void OnLoaded(object sender, RoutedEventArgs e) {
Debug.WriteLine(string.Format("View ({0})::Loaded", this.GetHashCode()));
}
private void OnUnloaded(object sender, RoutedEventArgs e) {
Debug.WriteLine(string.Format("View ({0})::Unloaded", this.GetHashCode()));
}
6) MainPage (XAML)
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
x:Name="Grid">
<Button x:Name="btnNewView"
Click="btnNewView_Click"
Content="New View"
Margin="4"/>
</Grid>
7) Add the event handler to the MainPage
private void btnNewView_Click(object sender, RoutedEventArgs e) {
Debug.WriteLine("Opening a new popup...");
View view = new View();
view.HorizontalAlignment = HorizontalAlignment.Center;
view.VerticalAlignment = VerticalAlignment.Center;
Popup popup = new Popup();
popup.Child = view;
popup.HorizontalOffset = 300;
popup.VerticalOffset = 300;
popup.IsOpen = true;
}
Opening and closing popups multiple times results the following output (Please keep track of hash codes):
Opening a new popup...
View (46418718)::ctor
ViewModel (59312528)::ctor
ViewModel (59312528)::Value::get
View (46418718)::Loaded
Closing the popup...
View (46418718)::Unloaded
Opening a new popup...
View (58892413)::ctor
ViewModel (61646925)::ctor
ViewModel (61646925)::Value::get
ViewModel (59312528)::Value::set
View (58892413)::Loaded
Closing the popup...
View (58892413)::Unloaded
Which means the setter for the ViewModel that is created in the Unloaded view model is being called that is a little bit strange. This behavior is the same for both x:Bind and Binding.
I would like to know if there is an explanation on this behavior.
To Clarify more:
A brand new pair of view/view-model instances are created each time but when the new view is being loaded, the setter on the previous instance of view-model is being called. The previous instance of the view is unloaded and should not even exist at that point. (Think of a popup that is being closed each time, and there is not event a reference the old view/view-model.)
Which means the setter for the ViewModel that is created in the Unloaded view
model is being called that is a little bit strange
Firstly, the setter is not called when the view unloaded, it is called when loading the view. You can add the Loading event handle to verify this. Adding loading event code to the code behind of view control as follows:
this.Loading += View_Loading;
private void View_Loading(FrameworkElement sender, object args)
{
Debug.WriteLine(string.Format("View ({0})::Loading", this.GetHashCode()));
}
And the output now will be:
Closing the popup...
View (22452836)::Unloaded
Opening a new popup...
View (58892413)::ctor
ViewModel (61646925)::ctor
View (58892413)::Loading
ViewModel (61646925)::Value::get
ViewModel (19246503)::Value::set
View (58892413)::Loaded
Secondly, we need to look into why setter is called in this scenario.
One is because you set the binding mode to TwoWay. If you remove this property as follows you will not see the setter called since the ViewModel doesn't need to know the changes in the view.
<RadioButton x:Name="rdo1" Content="Value1" IsChecked="{Binding Path=Value, Converter={StaticResource EnumTypeToBooleanConverter}, ConverterParameter=Value1, UpdateSourceTrigger=PropertyChanged}"/>
More details about binding mode please reference this article. Another reason may be the specific for RadioButton control. A RadioButton can be cleared by clicking another RadioButton in the same group, but it cannot be cleared by clicking it again. So when set IsChecked property to true, we thought the property value of the group is updated. This will trigger the TwoWay binding. In your scenrio, you can test this by setting the default value of IsChecked to false as follows, and you will find the setter is not called until you select the rdo1 on the UI. Or you can use another control CheckBox for testing which will also not call the setter until IsChecked value updated.
public class EnumTypeToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return EnumType.Value1;
}
}
The behavior is NOT the same if ScrollViewer gets removed from the View
The behavior is NOT the same for lets say a Boolean property
For these two scenarios, I also tested on my side. The result is the same with the outputs above. Since I don't know how you bind the Boolean property, as I mentioned, whether setter is called depend on what the binding mode is and whether you set or update the property. My testing code about binding Boolean is as follows which have same outputs.
View.xaml
<RadioButton x:Name="rdo2"
Content="BoolValue"
IsChecked="{Binding Path=BoolValue, Converter={StaticResource EnumTypeToBooleanConverter}, ConverterParameter=Value1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Converter:
public class EnumTypeToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return true;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
//return EnumType.Value1;
return true;
}
}
ViewModel;
private bool boolvalue;
public bool BoolValue
{
get
{
Debug.WriteLine(string.Format("ViewModel ({0})::boolvalue::get", this.GetHashCode()));
return this.boolvalue;
}
set
{
Debug.WriteLine(string.Format("ViewModel ({0})::boolvalue::set", this.GetHashCode()));
if (this.boolvalue != value)
{
this.boolvalue = value;
this.OnPropertyChanged();
}
}
}

UserControl InitializeComponents() method set the Content property, but does NOT trigger binding update

For example, there is a class MyUserControlBase derived from UserControl and within its constructor there is set of binding for Content dependency property.
MyUserControlBase.cs
namespace BindingBeforeInitComp
{
using System.Diagnostics;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
public class MyUserControlBase : UserControl
{
public static readonly DependencyProperty MyContentProperty = DependencyProperty.Register("MyContent",
typeof(object),
typeof(MyUserControl),
new PropertyMetadata(null, MyContentChangedCallback));
public MyUserControlBase()
{
// Set binding to the Content property.
var propertyPath = new PropertyPath("Content");
var binding = new Binding { Path = propertyPath, Source = this };
SetBinding(MyContentProperty, binding);
}
public object MyContent
{
get { return GetValue(MyContentProperty); }
set { SetValue(MyContentProperty, value); }
}
private static void MyContentChangedCallback(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
Debugger.Break(); // Breaking here to show that binding to Content property has updated.
}
}
}
There is also MyUserControl(cs+xaml) derived from MyUserControlBase. In the constructor there is default IntializeComponents() method (Note: binding was set before this method in the base constructor). Before calling of IntializeComponents() the Content property is null, after calling Content is set to content described in xaml. But binding does not update a target property.
MyUserControl.xaml.cs
namespace BindingBeforeInitComp
{
using System.Diagnostics;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
public sealed partial class MyUserControl : MyUserControlBase
{
public MyUserControl()
{
Debugger.Break(); // Here Content is null.
InitializeComponent();
Debugger.Break(); // Here Content is set, but as you can see there is no binding update.
}
private void ChangeContentClick(object sender, RoutedEventArgs e)
{
// Change content by a click. In this case the property changing should trigger update.
Content = new Grid() { Background = new SolidColorBrush(Colors.Chartreuse) };
}
}
}
MyUserControl.xaml
<local:MyUserControlBase
x:Class="BindingBeforeInitComp.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BindingBeforeInitComp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid Background="OrangeRed">
<Button Click="ChangeContentClick" HorizontalAlignment="Center" Width="200" Height="150" Background="#FF00AE4F">Change content</Button>
</Grid>
</local:MyUserControlBase>
Full sources: https://github.com/inTagger/Bugs/tree/master/BindingBeforeInitComp
UPDATE: I have updated sources on the GitHub, added WPF (NET45) project to demonstrate the true way how dependency properties and bindings should work. And yes, WPF has no such problem (behavior/feature/bug).
UPDATE2: If Content dependency property is set directly in constructor - binding works, but not if Content is set somewhere inside InitializeComponents().
The InitializeComponent method's job is to load your Xaml, so it's to be expected that none of your UI elements are available until after it has run.
Consider handling the Loaded event - you can attach a handler to that in your base constructor, and it won't be raised until after the object is fully constructed.

How can I specify a ViewModel to a XAML window?

I am trying to share a ViewModel between XAML windows. This is necessary to allow multiple views of the object instance to receive events from the ViewModel.
Specifying the ViewModel as a resource in the XAML, then overwriting it in an alternate constructor does not work. The binding will still be to the default instance created in the default constructor and will not receive events from or update the proper instance.
This does not work:
MyWindow.xaml:
<Window x:Class="MyNamespace.MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyNamespace"
Title="My Window"
Width="700" Height="550">
<Window.Resources>
<local:MyViewModel x:Key="MyModel"/>
</Window.Resources>
<ContentPresenter Content="{StaticResource MyModel}"/>
</Window>
MyWindow.xaml.cs
imports ...;
namespace MyNamespace {
public partial class MyWindow {
public MyWindow() {
InitializeComponent();
}
public MyWindow(MyViewModel model)
: this() {
Resources["MyModel"] = model;
}
}
}
Nor will this:
MyWindow.xaml.cs
imports ...;
namespace MyNamespace {
public partial class MyWindow {
public MyWindow()
: this(new MyViewModel()) { }
public MyWindow(MyViewModel model) {
Resources["MyModel"] = model; // Resources not yet initialized!
InitializeComponent();
}
}
}
if you are using Microsoft.Practices.Unity you can use TransientLifetimeManager.It will make sure that only one object of your viewmodel is created.
MyThis can be done by using properties on the code-behind and using the Binding tag in the XAML rather than StaticResource as follows:
MyWindow.xaml:
<Window x:Class="MyNamespace.MyWindow"
x:Name="this"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyNamespace"
Title="My Window"
Width="700" Height="550">
<ContentPresenter Content="{Binding MyModel, ElementName=this}"/>
</Window>
MyWindow.xaml.cs
imports ...;
namespace MyNamespace {
public partial class MyWindow {
public MyViewModel MyModel { get; private set; }
public MyWindow()
: this(new MyViewModel()) { }
public MyWindow(MyViewModel model) {
MyModel = model;
InitializeComponent();
}
}
}
Multiple windows (or other components) can use the same model instance.
Edit 06-Dec-12:
The XAML was not correct and the binding would not work. Added the x:Name attribute to the root element (Window), and added the ElementName argument to the Content attribute of the bound element (ContentPresenter).