Cannot bind to public property inside ResourceDirectory WinUI3/UWP - c#

So I am currently trying to bind a public static SolidColorBrush to a "Filled" property on a Rectangle inside a ResourceDirectory Style (inisde App.xaml).
But I always get the Exception
"Type 'x' used after '{' must be a Markup Extention. Error code 0x09c6."
My xaml code looks like this (inside App.xaml):
<Rectangle
x:Name="SelectionIndicator"
Width="4"
Height="24"
Fill="{x:Bind SomeNamespace:SomeClass.SomeColor}"/>
and my codebehind looks like this (inside public class called "SomeClass" in Namespace called "SomeNamespace"):
public static SolidColorBrush SomeColor = new(Colors.Red);
I know its possible to bind to a public static property with x:Bind in WinUI/UWP but this somehow doesnt work.
I really need to bind from some code behind so I can implement INotifyPropertyChanged so I cant just create a new color resource.
What is the correct way to implement my wanted behavior?
Thanks in advance!

You probably can't call your PropertyChanged event handler from a static property. You'll need an instance.
Consider storing the brush as a non-static property in a wrapper class. You can instantiate this class as a resource in your App.xaml file and use it across your application.
ColorStorer is a class to store the SolidColorBrush:
public class ColorStorer: INotifyPropertyChanged
{
private SolidColorBrush scb = new SolidColorBrush(Colors.Red);
public SolidColorBrush Scb
{
get
{
return scb;
}
set
{
if (value as SolidColorBrush != null)
{
scb = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Instantiate it in Application.Resources in App.xaml:
<Application.Resources>
<ResourceDictionary>
<local:ColorStorer x:Key="TestAppColorStorer" />
</ResourceDictionary>
</Application.Resources>
Use it in your views and controls:
<Rectangle
Width="100"
Height="50"
HorizontalAlignment="Center"
Fill="{Binding Scb, Source={StaticResource TestAppColorStorer}, Mode=OneWay}" />

Related

MVVM changing grid's background color on click

I'm a true beginner in MVVM pattern. I'm trying to change the backgound of a grid on button's click. I have a xaml with a grid containing a button, and a ViewModel .cs from where i want to change the grid's background on button's click. Until there i just succeed to show up a MessageBox when i clicked...
.xaml code:
<Window x:Class="WpfSimple.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfSimple"
Title="MainWindow" Height="150" Width="370">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Button Content="Click"
Height="23"
HorizontalAlignment="Left"
Background="Gray"
Margin="75.944,47.465,0,0"
Name="btnClick"
VerticalAlignment="Top"
Width="203"
Command="{Binding ButtonCommand}"/>
<!--What is necessary to add for changing grid color ? Commandparameter ?-->
</Grid>
MainWindowViewModel.cs code:
namespace WpfSimple
{
class MainWindowViewModel
{
private ICommand m_ButtonCommand;
public ICommand ButtonCommand
{
get
{
return m_ButtonCommand;
}
set
{
m_ButtonCommand = value;
}
}
public MainWindowViewModel()
{
ButtonCommand=new RelayCommand(new Action<object>(ChangeBgColor));
}
public void ChangeBgColor(object obj)
{
/*HERE I WANT TO CHANGE GRID COLOR*/
}
}
}
Sorry for my bad english.
Best regards.
Fitst of all you should implement INotifyPropertyChanged in your ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then, add NotifyPropertyChanged() to your properties setter.
Ok. Next, add new Property with your grid background color to your ViewModel:
private Brush _gridBackground;
public Brush GridBackground
{
get { return _gridBackground; }
set
{
_gridBackground = value;
NotifyPropertyChanged();
}
}
And bind your grid's background to your property:
<Grid Background="{Binding GridBackground}">
Finally you can just change GridBackground in the command handler:
public void ChangeBgColor(object obj)
{
GridBackground = Brushes.Blue;
}
You should remember that it's a bad practice to add WPF-classes like Brush to your code. The better way is to use IValueConverter in XAML code and BCL classes in your ViewModel. For example, you can use enumeration in ViewModel and convert it to brush in ValueConverter.
Add new enum for your ViewModel's property:
public enum GridState { Valid, Invalid }
Change property type:
private GridState _gridBackground;
public GridState GridBackground
{
get { return _gridBackground; }
set
{
_gridBackground = value;
NotifyPropertyChanged();
}
}
Add new class with value converter
public class GridStateToBackgroundColorConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
GridState val = (GridState) value;
if(val == GridState.Valid)
return Brushes.Green;
return Brushes.Red;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
Add new static resource to your control
<UserControl.Resources>
<converters:GridStateToBackgroundColorConverter x:Key="gridStateToBackgroundColorConverter" />
</UserControl.Resources>
Update binding to your property
<Grid Background="{Binding GridBackground, Converter={StaticResource gridStateToBackgroundColorConverter}">
If you want to change the grid background color then you can use command parameter. You can pass any UI control as Command parameter. In your case pass grid to access grid in your view model.
Give the name to your grid and use that name to use as command parameter.
For this you need to implement the code like this:
<Window x:Class="WpfSimple.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfSimple"
Title="MainWindow" Height="150" Width="370">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid Name="grid">
<Button Content="Click"
Height="23"
HorizontalAlignment="Left"
Background="Gray"
Margin="75.944,47.465,0,0"
Name="btnClick"
VerticalAlignment="Top"
Width="203"
Command="{Binding ButtonCommand}"
CommandParameter="{Binding Elementname="grid"}"/>
</Grid>
After making this change to the .xaml file. Implement Parameterized Relay Command to use this passed Grid to use in your Viewmodel file.
To Implement Parameterized Relay Command Try to Implement following code:
private ICommand m_ButtonCommand;
public ICommand ButtonCommand
{
get
{
return m_ButtonCommand;
}
set
{
m_ButtonCommand = value;
}
}
public MainWindowViewModel()
{
ButtonCommand=new RelayCommand<Grid>(ChangeBgColor);
}
public void ChangeBgColor(Grid grid)
{
if(grid!=null)
grid.Background = Brushes.Red; //Any color you want to change.
}
I hope this will work. Thank you.

Change The Binding Source To Another CLR Object

This is a mostly out of curiosity question and to hopefully help me better understand binding, XAML, and extension syntax.
So I simply want to change the binding source from the MainWindow to an object I have instantiated in MainWindow.
Here is my C# code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
favclass myfavclass = new favclass();
InitializeComponent();
this.DataContext = this;
}
string _myString = "hello";
public string MyString
{
get { return _myString; }
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
}
public class favclass : INotifyPropertyChanged
{
int _myint = 34;
public int MyInt
{
get { return _myint; }
set { _myint = value; }
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
}
}
and my XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" >
<Grid>
<TextBlock Height="50" Width="50" Text="{Binding MyString}"/>
<TextBlock Height="50" Width="48" Margin="200,100,100,100"
Text="{Binding Source=myfavclass, Path=MyInt}"/>
</Grid>
</Window>
So as you can see I want first display the MyString property from main window.
Then I want to display the MyInt from the myfavclass object. But of course MyInt doesn't appear. I've tried every variation I can think of.
What XAML am I missing? Why doesn't the XAML I have work?
Thanks
Source=myfavclass this is wrong. Source can be only assigned directly using element syntax like this:
<Binding>
<Binding.Source>
<!-- value here -->
</Binding.Source>
</Binding>
Or you can use StaticResource or DynamicResoure or some custom MarkupExtension like this:
Text="{Binding Source={StaticResource someKey}, Path=MyInt}"
Or use the new feature {x:Reference} to get reference directly to some named element inside XAML:
Text="{Binding Source={x:Reference someName}, Path=MyInt}"
Moreover the myfavclass is declared as local variable inside your code behind. There is no way it can be used (referenced) inside XAML code.
You're doing something called multiple viewmodels. If so you should provide multiple DataContext for your controls. I prefer to using nested viewmodels. To implement this, you can try modifying the MainWindow like this:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
_myfavclass = new favclass();
InitializeComponent();
this.DataContext = this;
}
private readonly favclass _myfavclass;
//we will use this property inside XAML code
public favclass MyFavClass {
get {
return _myfavclass;
}
}
}
Now in XAML code, you can bind the Text to the MyFavClass.MyInt, note that the DataContext is implicitly the source for the Binding, so you just need to specify the Path:
<TextBlock Height="50" Width="48" Margin="200,100,100,100"
Text="{Binding Path=MyFavClass.MyInt}"/>
Your MyInt is not properly implemented using INotifyPropertyChanged (but I hope you already know that).
favclass myfavclass = new favclass(); should be declared out of the init method,or you won't get this.myfavclass instance

Modify ObjectDataProvider

I have an application where I am using a ObjectDataProvider (App.xaml):
<Application x:Class="Example.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:Example.Settings"
StartupUri="MainWindow.xaml"
Startup="Application_Startup"
DispatcherUnhandledException="ApplicationDispatcherUnhandledException">
<Application.Resources>
<ObjectDataProvider x:Key="odpSettings" ObjectType="{x:Type src:AppSettings}"/>
</Application.Resources>
My class is:
class AppSettings : INotifyPropertyChanged
{
public AppSettings()
{
}
Color itemColor = Colors.Crimson;
public Color ItemColor
{
get
{
return itemColor;
}
set
{
if (itemColor == value)
return;
itemColor = value;
this.OnPropertyChanged("ItemColor");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then I have a userControl where I am using that color, for example:
<Border Background="{Binding Source={StaticResource odpSettings},
Path=ItemColor,Mode=TwoWay}" />
I am adding that UserControl to my MainWindow where I have a ColorPicker control and I would like to modify the Border Background color of my UserControl with the ColorPicker color selected.
I tried something like this:
AppSettings objSettings = new AppSettings();
objSettings.ItemColor = colorPicker.SelectedColor;
When I change the color using the ColorPicker, the color in my UserControl doesn't change, I guess this is because I am creating a new instance of the class AppSettings.
Is there a way to accomplish what I am trying to do?
Thanks in advance.
Alberto
Thanks for the comments, I used the next code:
AppSettings objSettings = (AppSettings)((ObjectDataProvider)Application.Current.FindResource("odpSettings")).ObjectInstance;
That way I can access and modify the value of the property ItemColor.
Also I change the property type to SolidColorBrush.
objSettings.ItemColor = new SolidColorBrush(colorPicker.SelectedColor);

Setting an object property with XAML

I have two custom UserControl in my project code: TableControl and DeckControl. In the code of the latter I would be able to access the former when it's needed. So in my DeckControl I implemented the following property:
private TableControl m_Table;
public TableControl Table
{
get { return m_Table; }
set { m_Table = value; }
}
The problem is that I'm not able to set the property from XAML code:
<Canvas Core:Name="Layout" Loaded="OnLayoutLoaded">
<Namespace:TableControl Core:Name="Table" Canvas.Left="0" Canvas.Top="0" Height="{Binding ElementName=Layout, Path=ActualHeight}" Width="{Binding ElementName=Layout, Path=ActualWidth}"/>
<Namespace:DeckControl Core:Name="Deck" Canvas.Left="50">
</Canvas>
I tried using Reference but compiler says that the method or opera
<Namespace:DeckControl Core:Name="Deck" Canvas.Left="50" Table="{Core:Reference Name=Table}">
I tried this but it isn't working either:
<Namespace:DeckControl Core:Name="Deck" Canvas.Left="50" Table="{Core:Static Table}">
I also tried using Binding:
<Namespace:DeckControl Core:Name="Deck" Canvas.Left="50" Table="{Binding ElementName=Table}">
Ok so... it's my first approach to XAML and I'm still working on it... but I really can't get it!
If you want to bind to a property in youe Model(window/Usercontrol) codebehind you have to set the DataContext in your Xaml.
There are may ways to do this but the simpliest is just naming your window or usercontrol and binding using ElementName.
Example for a Window:
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="233" Width="143" Name="UI">
<Canvas DataContext="{Binding ElementName=UI}" > <!-- Set dataContext to Window -->
<Namespace:DeckControl Canvas.Left="50" Table="{Binding ElementName=Table}">
</Canvas>
</Window>
And if you want the Xaml to update when Table changes your code behind should implement INotifyPropertyChanged, this will inform the Xaml that the property has changed.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
}
private TableControl m_Table;
public TableControl Table
{
get { return m_Table; }
set { m_Table = value; NotifyPropertyChanged("Table"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
If your Table property is not a DependancyProperty you will have to chage this so you can bind.
Example:
public class DeckControl : UserControl
{
.......
// Using a DependencyProperty as the backing store for Table. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TableProperty =
DependencyProperty.Register("Table", typeof(TableControl), typeof(DeckControl), new UIPropertyMetadata(null));
public TableControl Table
{
get { return (TableControl)GetValue(TableProperty); }
set { SetValue(TableProperty, value); }
}
}
Also any property that is being binded outside the scope of the UserControl has to be a DependancyProperty.
Example:
public partial class DeckControl : UserControl
{
public DeckControl()
{
InitializeComponent();
}
private int myVar;
public int MyProperty
{
get { return myVar; }
set { myVar = value; }
}
}
This will bind inside the usercontrol when it is a simple property as it is inscope.
<UserControl .....
d:DesignHeight="300" d:DesignWidth="300" Name="UI">
<TextBlock Text="{Binding MyProperty}" />
</UserControl>
This will not bind as its out of scope of the UserControl, MyProperty will have to be a DependancyProperty to bind here
<Window ....
Title="MainWindow" Height="233" Width="143" Name="UI">
<Grid>
<local:DeckControl MyProperty="{Binding Width}" /> // Will not bind
</Grid>
</Window>
Hope that makes sense :)

WPF UserControl Binding Problem

I like to create a UserControl with own Header Property.
public partial class SomeClass: UserControl, INotifyPropertyChanged
{
public SomeClass()
{
InitializeComponent();
}
private string header;
public string Header
{
get { return header; }
set
{
header = value;
OnPropertyChanged("Header");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
in UserContol xaml:
Label Name="lbHeader" Grid.Column="0" Content="{Binding Path=Header}"
If I set the value: AA2P.Header = "SomeHeeaderText"; than the label.Caption will not changed. How can I solve that problem?
In Windows xaml:
uc:SomeClass x:Name="AA2P"
If I give directly a value to label (lbHeader.Content = header;) instead of OnPropertyChanged("Header"); its work but, why it does not work with OnPropertyChanged?
I need to use DataContext for somethig else. I try to use dependency property but something is wrong.
public partial class tester : UserControl
{
public tester()
{
InitializeComponent();
}
public string Header
{
get { return (string)GetValue(MyDependencyProperty); }
set { SetValue(MyDependencyProperty, value); }
}
public static readonly DependencyProperty MyDependencyProperty =
DependencyProperty.Register("MyDependencyProperty", typeof(string), typeof(string));
}
<UserControl ... x:Name="mainControl">
<TextBlock Text="{Binding ElementName=mainControl, Path=MyDependencyProperty}"/>
</UserControl>
<Window ...>
<my:tester Header="SomeText" />
</Window>
It does not work. What I do wrong?
Thanks!
The easiest approach is to just the DataContext of your object. One way of doing that is directly in the constructor like this:
public SomeClass()
{
InitializeComponent();
DataContext = this;
}
Setting the DataContext will specify where new data should be fetched from. There are some great tips and information in the article called WPF Basic Data Binding FAQ. Read it to better understand what the DataContex can be used for. It is an essential component in WPF/C#.
Update due to update of the question.
To my understanding you should change the first argument of DependencyProperty.Register to the name of the property that you want to bind to, here "Header" as well as the second argument to the type of your class, here SomeClass. That would leave you with:
public static readonly DependencyProperty MyDependencyProperty =
DependencyProperty.Register("Header", typeof(SomeClass), typeof(string));
But i seldom use dependency properties so I am not positive that this is it, but its worth a try..
If you need the Data context for something else. You can also utilize the ElementName property in the Binding.
<UserControl
x:Class="MyControl.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="mainControl">
<TextBlock Text="Binding ElementName=mainControl, Path=MyDependencyProperty}"/>
</UserControl>
[Edit]
I should add something. Make the "Header" property a dependency property, this will make your live much easier. In UI Controls you should make property almost always a dependency property, every designer or user of your control will thank you.
The UserControl itself needs the DataContext of where it is used later. But the controls inside the UserControl need the UserControl as their DataContext, otherwise they also will inherit the DataContext from the later usage context. The trick is to set the DataContext of the UserControl's child to that of the UserControl, so it now can use the dependency properties of the UserControl.
<UserControl x:Class="MyControl.MyUserControl">
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType=UserControl,AncestorLevel=1}}">...</Grid>
</UserControl>
If you do this this way the children of the Grid can have simple {Binding dp's name} without additionally ElementName parameters.

Categories

Resources