I'm attempting to databind the ListBox SelectedItems attribute using an attached property I've created. I set up a class called ListBoxFix which is located in a folder called ControlFixes. It's code is a very simple dependency property shown below:
using System.Windows;
using System.Windows.Controls;
namespace QMAC.ControlFixes
{
public static class ListBoxFix
{
public static bool GetSelectedItemsBinding(ListBox element)
{
return (bool)element.GetValue(SelectedItemsBindingProperty);
}
public static void SetSelectedItemsBinding(ListBox element, bool value)
{
element.SetValue(SelectedItemsBindingProperty, value);
if (value)
{
element.SelectionChanged += (sender, args) =>
{
var x = element.SelectedItems;
};
}
}
public static readonly DependencyProperty SelectedItemsBindingProperty =
DependencyProperty.RegisterAttached("FixSelectedItemsBinding",
typeof(bool), typeof(FrameworkElement), new PropertyMetadata(false));
}
}
In my XAML code I have the following markup:
<Window x:Class="QMAC.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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
xmlns:fix="clr-namespace:QMAC.ControlFixes"
x:Name="Window"
DataContext="{Binding Main, Mode=OneWay, Source={StaticResource Locator}}"
Title="QMAC" Width="554.779" ResizeMode="CanMinimize" Height="539" Icon="logo.ico" >
<Grid Background="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" RenderTransformOrigin="0.593,0.948" Margin="0,0,0,1">
<ListBox x:Name="schoolListBox" HorizontalAlignment="Left" Margin="25,86,0,0" Width="274" FontSize="16" SelectionMode="Extended" ItemsSource="{Binding LocationList}" fix:ListBox.SelectedItemsBindingProperty="true" VerticalAlignment="Top" Height="364"></ListBox>
</Grid>
</Window>
Unfortunately, I'm getting the 3 errors to how I've setup my markup. They are
Error 1 The name "ListBox" does not exist in the namespace "clr-namespace:QMAC.ControlFixes".
Error 2 The attachable property 'SelectedItemsBindingProperty' was not found in type 'ListBox'.
Error 3 The property 'ListBox.SelectedItemsBindingProperty' does not exist in XML namespace 'clr-namespace:QMAC.ControlFixes'.
I'm mainly trying to understand why it's looking for ListBox in my ControlFixes namespace?
You declare and use the attached property in a wrong way. I would suggest you to read carefully this well written overview.
There are following mistakes in your code:
The owner type for your attached property is incorrectly specified as FrameworkElement.
The registered property name does not match the static field containing it
You try to use your attached property via the ListBox class although you have defined it in your ListBoxFix class.
The proper attached property definition should look similar to this:
public static class ListBoxFix
{
public static bool GetSelectedItemsBinding(ListBox element)
{
return (bool)element.GetValue(SelectedItemsBindingProperty);
}
public static void SetSelectedItemsBinding(ListBox element, bool value)
{
element.SetValue(SelectedItemsBindingProperty, value);
}
public static readonly DependencyProperty SelectedItemsBindingProperty =
DependencyProperty.RegisterAttached("SelectedItemsBinding",
typeof(bool), typeof(ListBoxFix), new PropertyMetadata(false));
}
Note that the ownerType parameter of the RegisterAttached() method provides the type of your class containing the attached property. Take a look on the name parameter too.
The proper usage of your attached property:
<ListBox fix:ListBoxFix.SelectedItemsBinding="true"/>
Update:
You might want to use your attached property in a "WPF" style. Then it would be better to design your class to be derived from DependencyObject. This is what MSDN states:
If your class is defining the attached property strictly for use on other types, then the class does not have to derive from DependencyObject. But you do need to derive from DependencyObject if you follow the overall WPF model of having your attached property also be a dependency property.
Related
I have the object MyObject Object in the class MainClass.xaml.
I want to pass this object to a ViewModel class of a nested user control called SubSubUserControl through a SubUserControl.
Here is the code in MainClass.xaml:
<UserControl x:Class="Test.SubUserControl"
d:DataContext="{d:DesignInstance d:Type=local:SubUserControlViewModel}">
<Grid>
<local:SubSubUserControl Object="{Binding Object, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SubUserControl}}}"/>
</Grid>
</UserControl>
In SubUserControl I have a classical DependencyProperty in order to pass Object to it.
public static readonly DependencyProperty ObjectProperty = DependencyProperty.Register(nameof(Object), typeof(Object), typeof(SubUserControl));
public Object Object
{
get => (Object)GetValue(ObjectProperty);
set => SetValue(ObjectProperty, value);
}
protected override void OnInitialized(EventArgs e)
{
InitializeComponent();
DataContext = new SubUserControlViewModel(Object);
base.OnInitialized(e);
}
SubUserControl is only a "bridge", the object Object is needed in SubSubUserControl.
I pass to it with a binding (changing Datacontex beacause the standard DataContext for SubUserControl is his ViewModel).
<local:SubSubUserControl Object="{Binding Object, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
and SubSubUserControl has the classical DependencyProperty.
SubSubUserControl has a ViewModel as DataContext and I'd like to pass that object to it.
I try with
public static readonly DependencyProperty ObjectProperty = DependencyProperty.Register(nameof(Object), typeof(Object), typeof(SubSubUserControl));
public Object Object
{
get => (Object)GetValue(ObjectProperty);
set => SetValue(ObjectProperty, value);
}
protected override void OnInitialized(EventArgs e)
{
InitializeComponent();
DataContext = new SubSubUserControlViewModel(Object);
base.OnInitialized(e);
}
but Object is null.
The same code in SubUserControl works.
Is there a way to pass an object using binding to a ViewModel class that belongs to a nested UserControl?
I saw a lot of similar questions but noone works with my specific case...
EDIT
The class Object is instanced code-behind in MainClass.xaml.cs. In order to pass it I set the DataContext of MainClass to Self.
public MyObject Object { get; set; } = new MyObject();
Here is how I set DataContext in MainClass.xaml
<Window x:Class="Test.MainWindow"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<local:SubUserControl Object="{Binding Object}"/>
</Grid>
</Window>
Setting the DataContext to the entire Window avoid me to write something like ElementName or Relative Source.
P.s. Resharper do not warn me about any DataContext, each instance is seen correctly.
I don't know the method OnInitialized, but from the documentation :
Raises the Initialized event.
And the event Initialized :
Whether you choose to handle Loaded or Initialized depends on your requirements. If you do not need to read element properties, intend to reset properties, and do not need any layout information, Initialized might be the better event to act upon. If you need all properties of the element to be available, and you will be setting properties that are likely to reset the layout, Loaded might be the better event to act upon.
I think the binding work, but you try to read the value in OnInitialized that is called before the binding is resolved.
As suggested in the documentation, maybe you can use the event Loaded like :
public partial class SubSubUserControl : UserControl
{
public SubSubUserControl()
{
InitializeComponent();
Loaded += SubSubControl_Loaded;
}
private void SubSubControl_Loaded(object sender, RoutedEventArgs e)
{
DataContext = new SubSubViewModel(TargetFoo);
}
}
A alternative is to update the data context when the dependency property is modified :
public partial class SubSubUserControl : UserControl
{
public static readonly DependencyProperty ObjectProperty = DependencyProperty.Register(
"Object", typeof(MyObject),
typeof(SubSubUserControl),
new PropertyMetadata(OnObjectChanged)
);
private static void OnObjectChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if(sender is SubSubControl ssc)
{
ssc.DataContext = new SubSubViewModel((MyObject)e.NewValue);
}
}
}
EDIT from exchanges in comments.
It looks like you have a binding problem too. I suspect that a other element of type UserControl encapsulate the SubSubUserControl, like :
MainClass
SubUserControl
OtherUserControl
SubSubUserControl
In this case, the binding will use OtherUserControl as source (and not the expected SubUserControl).
In this case, you can specify in the binding the source's type is SubUserControl to avoid a other element of type UserControl is selected :
<local:SubSubUserControl Object="{Binding BridgeFoo, RelativeSource={RelativeSource AncestorType={x:Type local:SubUserControl}}}" />
A alternative is to use Binding.ElementName instead of Binding.RelativeSource :
<UserControl x:Class="ProjectNamespace.SubUserControl"
...
xmlns:local="clr-namespace:ProjectNamespace"
Name="Sub">
<local:SubSubUserControl Object="{Binding Object, ElementName=Sub}" />
</UserControl>
I prefer this, because I never remember the RelativeSource syntax.
I come from a WPF background so I thought I'd experiment with building a to-do app in WinUI 3. The app structure is a little overdesigned as I'm trying build it out like a more complex app. For that reason I have a ToDoTaskView and ToDoTaskViewModel, along with a MainWindowView and MainWindowViewModel even though it'd be much easier to build the entire app in a single XAML file.
The ToDoTaskView has a delete button, but the delete command lives on the MainWindowViewModel, as that's where the list that it must be deleted from lives. I think this a pretty common pattern where a sub-view needs to send a command to a parent view model.
The (abridged) MainWindowView:
<UserControl>
<ItemsRepeater ItemsSource="{Binding Tasks}">
<DataTemplate>
<local:ToDoTaskView />
</DataTemplate>
</ItemsRepeater>
</UserControl>
And the (heavily abridged) ToDoTaskView:
<UserControl>
<Button Command="???">Delete</Button>
</UserControl>
In WPF there's many ways to deal with this.
RoutedCommand
My prefered method. The MainWindowView can listen for a custom ToDoTaskDeleted routed command and bind to the command on the view model. Then any UI element anywhere underneath MainWindowView can fire said event and rest easy knowing it'll be handled somewhere above it on the visual tree.
There's no RoutedCommand in WinUI 3, and even worse, routed events are locked down and you can't define custom ones. So even building a custom RoutedCommand implementation would be difficult.
DynamicResource
I can define a StandardUICommand in MainWindowView.Resources, bind it to the command in the view model, then in ToDoTaskView I can use {DynamicResource DeleteCommand} to have the resource system search up the visual tree for the command.
Except I can't. WinUI3 doesn't have DynamicResource, only StaticResource. And since the two views are in different XAML files, and ToDoTaskView in a templated context, StaticResource can't resolve the resource name between them.
I think this could work for resources in App.xaml, but I'd rather not shove every command into the top level scope instead of keeping them where they belong.
All the commanding examples in the Microsoft docs seem to assume that the button and handler are in the same file, or they directly pass a reference to the command through to the child view's DataContext.
RelativeAncestor
Peter below reminded me that I tried this too, and found it's missing in WinUI 3. RelativeSource doesn't support any kind of ancestor discovery.
Manual Kludge
Setting up a direct reference from ToDoTaskViewModel to MainWindowViewModel is certainly possible, but I hate it. After all, who's to guarantee that this particular to do item is part of a list at any one moment? Maybe it lives in a pop-up dialog as a reminder? Handling this kind of thing through the visual tree is the Correct(tm) way to do it.
I wouldn't accept a PR from a coworker on my WPF project with this solution. But I can't seem to find any better way in WinUI 3.
Have I missed something about WinUI 3? Is it just not mature enough yet to have a solution? It seems like this scenario isn't so uncommon that it would be completely unsupported.
In this case, I'd create an ICommand dependency property, DeleteCommand and and bind a command in the view model. Here's a sample code using the CommunityToolkit.Mvvm NuGet package.
MainWindow.xaml
The MainWindow is named, "ThisWindow" in this case, so we can access its ViewModel from the ItemTemplate.
The DeleteCommandParameter is bound to the DataContext of the item, ToDoTaskViewModel in this case.
<Window
x:Class="ToDoApp.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:local="using:ToDoApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="ThisWindow"
mc:Ignorable="d">
<Grid RowDefinitions="Auto,*">
<StackPanel
Grid.Row="0"
Orientation="Horizontal">
<TextBox x:Name="NewToDo" />
<Button
Command="{x:Bind ViewModel.AddToDoCommand}"
CommandParameter="{x:Bind NewToDo.Text, Mode=OneWay}"
Content="Add" />
</StackPanel>
<ScrollViewer Grid.Row="1">
<ItemsRepeater ItemsSource="{x:Bind ViewModel.ToDoTasks, Mode=OneWay}">
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="local:ToDoTaskViewModel">
<local:ToDoTaskView
DeleteCommand="{Binding ElementName=ThisWindow, Path=ViewModel.DeleteToDoCommand}"
DeleteCommandParameter="{x:Bind}"
ToDo="{x:Bind ToDo, Mode=OneWay}" />
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
</Grid>
</Window>
MainWindow.xaml.cs
using Microsoft.UI.Xaml;
namespace ToDoApp;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public MainWindowViewModel ViewModel { get; } = new();
}
MainWindowViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
namespace ToDoApp;
[ObservableObject]
public partial class MainWindowViewModel
{
[ObservableProperty]
private ObservableCollection<ToDoTaskViewModel> toDoTasks = new();
[RelayCommand]
private void AddToDo(string todo)
{
ToDoTasks.Add(new ToDoTaskViewModel() { ToDo = todo });
}
[RelayCommand]
private void DeleteToDo(ToDoTaskViewModel toDoTask)
{
ToDoTasks.Remove(toDoTask);
}
}
ToDoTaskView.xaml
<UserControl
x:Class="ToDoApp.ToDoTaskView"
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:local="using:ToDoApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid ColumnDefinitions="*,Auto">
<TextBlock
Grid.Column="0"
Text="{x:Bind ToDo, Mode=OneWay}" />
<Button
Grid.Column="1"
Command="{x:Bind DeleteCommand, Mode=OneWay}"
CommandParameter="{x:Bind DeleteCommandParameter, Mode=OneWay}"
Content="Delete" />
</Grid>
</UserControl>
ToDoTaskView.xaml.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Windows.Input;
namespace ToDoApp;
public sealed partial class ToDoTaskView : UserControl
{
public static readonly DependencyProperty ToDoProperty = DependencyProperty.Register(
nameof(ToDo),
typeof(string),
typeof(ToDoTaskView),
new PropertyMetadata(default));
public static readonly DependencyProperty DeleteCommandProperty = DependencyProperty.Register(
nameof(DeleteCommand),
typeof(ICommand),
typeof(ToDoTaskView),
new PropertyMetadata(default));
public static readonly DependencyProperty DeleteCommandParameterProperty = DependencyProperty.Register(
nameof(DeleteCommandParameter),
typeof(object),
typeof(ToDoTaskView),
new PropertyMetadata(default));
public ToDoTaskView()
{
this.InitializeComponent();
}
public string ToDo
{
get => (string)GetValue(ToDoProperty);
set => SetValue(ToDoProperty, value);
}
public ICommand DeleteCommand
{
get => (ICommand)GetValue(DeleteCommandProperty);
set => SetValue(DeleteCommandProperty, value);
}
public object DeleteCommandParameter
{
get => (object)GetValue(DeleteCommandParameterProperty);
set => SetValue(DeleteCommandParameterProperty, value);
}
}
ToDoTaskViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
namespace ToDoApp;
[ObservableObject]
public partial class ToDoTaskViewModel
{
[ObservableProperty]
private string toDo = string.Empty;
}
Ok I have a solution. I cannot emphasize enough how much of a disgusting hack this is. Normally I'd be embarrassed to post this, but the only ones who should be embarrassed are Microsoft for publishing Win UI 3 in its current state and claiming it's capable of making real applications.
The gist of this is to mimic Ancestor-type RelativeSource binding in WPF. We create two attached properties - ParentContextViewType to specify the type of the ancestor we're looking for - and ParentContextView which is automatically assigned a reference to the desired parent view instance when the child loads. (I'd have made ParentContextView a readonly property, but of course, Win UI doesn't support that...) Then for the child button, we do a RelativeSource Self binding to the attached ParentContextView property, then adding the rest of the path, just like we would with a legit ancestor type bind.
Here goes (and may god have mercy on my soul):
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
namespace ParentBinding
{
public static class Hacks
{
public static DependencyProperty ParentContextView =
DependencyProperty.RegisterAttached(
"ParentContextView",
typeof(FrameworkElement),
typeof(Hacks),
new PropertyMetadata(null));
public static FrameworkElement GetParentContextView(DependencyObject d)
{
return d.GetValue(ParentContextView) as FrameworkElement;
}
public static void SetParentContextView(DependencyObject d, FrameworkElement view)
{
d.SetValue(ParentContextView, view);
}
public static DependencyProperty ParentContextViewTypeProperty =
DependencyProperty.RegisterAttached(
"ParentContextViewType",
typeof(Type),
typeof(Hacks),
new PropertyMetadata(null, (d, e) =>
{
if (!(d is FrameworkElement fe))
return;
if (e.OldValue != null)
fe.Loaded -= OnParentContextFeLoaded;
if (e.NewValue != null)
fe.Loaded += OnParentContextFeLoaded;
}));
private static void OnParentContextFeLoaded(object sender, RoutedEventArgs e)
{
if (!(sender is FrameworkElement fe))
return;
var type = GetParentContextViewType(fe);
if (type == null)
return;
while (!type.IsAssignableFrom(fe.GetType()) &&
(fe = VisualTreeHelper.GetParent(fe) as FrameworkElement) != null)
{
}
SetParentContextView(sender as DependencyObject, fe);
}
public static Type GetParentContextViewType(DependencyObject d)
{
return d.GetValue(ParentContextViewTypeProperty) as Type;
}
public static void SetParentContextViewType(DependencyObject d, Type val)
{
d.SetValue(ParentContextViewTypeProperty, val);
}
}
}
A use-case:
Model stuff:
using Microsoft.UI.Xaml.Input;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace ParentBinding
{
public class Command : ICommand
{
Action<object> _action;
public Command(Action<object> action)
{
_action = action;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter) => true;
public void Execute(object? parameter)
{
_action?.Invoke(parameter);
}
}
public class Parent
{
public ObservableCollection<Child> Children { get; set; }
private Command _deleteChildCommand;
public ICommand DeleteChildCommand =>
_deleteChildCommand ?? (_deleteChildCommand = new Command((p) =>
{
if (!(p is Child ch))
return;
this.Children.Remove(ch);
}));
}
public class Child
{
public string Name { get; set; }
public override string ToString() => this.Name;
}
}
Main Window:
<Window x:Class="ParentBinding.MainWindow"
x:Name="_main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ParentBinding"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<ListView DataContext="{Binding ElementName=_main, Path=Parent}"
ItemsSource="{Binding Children}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Child">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" />
<Button local:Hacks.ParentContextViewType="ListView"
Grid.Column="1"
CommandParameter="{Binding}"
Content="Delete"
Command="{Binding
Path=(local:Hacks.ParentContextView).DataContext.DeleteChildCommand,
RelativeSource={RelativeSource Self}}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Window>
using Microsoft.UI.Xaml;
namespace ParentBinding
{
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public Parent Parent { get; } = new Parent
{
Children = new System.Collections.ObjectModel.ObservableCollection<Child>
{
new Child
{
Name = "Larry"
},
new Child
{
Name = "Curly"
},
new Child
{
Name = "Moe"
}
}
};
}
}
Amazingly, it works, and one of the reasons I was so curious to try it and post it is that it is, more or less, a general purpose substitute for ancestor type binding in WinUI 3. Hope someone finds it useful.
This question already has answers here:
DataBinding in WPF?
(1 answer)
Binding to static property
(12 answers)
Closed 1 year ago.
Everything I've looked at on ComboBox binding shows different implementations than what I'm attempting to do, and my attempts at configuring it myself have failed.
Here's a simplistic example of what I'm trying to accomplish.
Code file:
namespace TestApp
{
public record ItemInfo
{
public int ID;
public string name;
public string description;
public decimal value;
public bool available;
}
public static class Data
{
public static ItemInfo[] myItems = new ItemInfo[10];
}
}
XAML:
<Window
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:TestApp"
x:Class="TestApp.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.DataContext>
<Binding Source="local:Data"/>
</Grid.DataContext>
<ComboBox Grid.Row="0"
VerticalAlignment="Center" HorizontalAlignment="Center"
Width="400" Height="40"
ItemsSource="{Binding myItems[]}" DisplayMemberPath="name">
</ComboBox>
</Grid>
</Window>
On a side note, lest there be confusion, I've got a separate method (not shown) that populates the myItems array. I know this is working and contains the desired data, as when the program is running I can put a watch on it and see the proper values in the array.
What I'm attempting to do is to have the ComboBox contain all of the Data.myItems[].name values. The above code shows my most recent attempt to bind it, and while it builds successfully and doesn't throw any errors, the ComboBox is still empty.
There is an intellisense ... error under the myItems[] binding stating "No data context found for Binding myItems{}"
I've also tried moving the myItems[] to the Grid.DataContext -- <Binding Source="local:Data:myItems[]} and set ItemsSource to simply {Binding Path="name"} to no avail.
Also tried creating a static Collection of ItemInfo records, and set the Grid data context binding to that collection.. to no avail
What's the key to binding to static classes/objects?
What's the key to binding to static classes/objects?
1) Getting the value of a static member
In order not to confuse oneself, it is better to make the bindable member a read-only property:
public static class Data
{
public static ItemInfo[] MyItems {get;} = new ItemInfo[10];
}
<ComboBox Grid.Row="0"
VerticalAlignment="Center" HorizontalAlignment="Center"
Width="400" Height="40"
ItemsSource="{x:Static local:Data.MyItems}"
DisplayMemberPath="name">
You can get the Enum value in the same way.
2) Binding a static property.
Binding means "tracking" changes in the value of the source property.
To do this, you need to create a static event.
An example of a static wrapper for a clock and binding to it:
using System.Threading;
public static class ClockForWpf
{
private static readonly Timer timer = new Timer(Tick, null, 0, 10);
private static void Tick(object state)
{
Time = DateTime.Now;
TimeChanged?.Invoke(null, EventArgs.Empty);
}
public static event EventHandler TimeChanged;
public static DateTime Time { get; private set; }
}
<TextBlock Text="{Binding Path=(local:ClockForWpf.Time)}"/>
Hello,
After reading lots of topics about visibility binding for hours, I'm asking here because I don't manage to make my case works.
I have a grid with a custom attached property (type System.Windows.Visibily) which I want to use to display (or not) a textblock inside the grid (by binding). Also I want to change the visibility everytime the custom attached property change.
What I have done so far :
CustomProperties class :
public static class CustomProperties
{
public static readonly DependencyProperty starVisibilityProperty =
DependencyProperty.RegisterAttached("starVisibility",
typeof(System.Windows.Visibility), typeof(CustomProperties),
new FrameworkPropertyMetadata(null));
public static System.Windows.Visibility GetStarVisibility(UIElement element)
{
if (element == null)
throw new ArgumentNullException("element");
return (System.Windows.Visibility)element.GetValue(starVisibilityProperty);
}
public static void SetStarVisibility(UIElement element, System.Windows.Visibility value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(starVisibilityProperty, value);
}
}
Then here is my xaml :
<Grid Name="server1State" Grid.Row="1" local:CustomProperties.StarVisibility="Hidden">
<TextBlock Name="server1Star" Text="" FontFamily="{StaticResource fa-solid}" FontSize="30" Margin="10" Foreground="#375D81" Visibility="{Binding ElementName=server1State, Path=server1State.(local:CustomProperties.starVisibility)}"/>
</Grid>
But when I run my app, the textblock is absolutely not hidden, this is visible, and never change. I have tried lots of things with Path and also INotifyPropertyChanged but as I am working with static custom attached property, I didn't manage to make it works.
Maybe some of you could help me, thanks.
Your Binding.Path on the TextBlock is wrong.
Since I've read from your comment, that you prefer to use a boolean property, I'll show how to convert the bool value to a Visibility enumeration value using the library's BooleanToVisibilityConverter.
I think you may already got it, but then got confused due to your wrong Binding.Path:
CustomProperties.cs
public class CustomProperties : DependencyObject
{
#region IsStarVisibile attached property
public static readonly DependencyProperty IsStarVisibileProperty = DependencyProperty.RegisterAttached(
"IsStarVisibile",
typeof(bool),
typeof(CustomProperties),
new PropertyMetadata(default(bool)));
public static void SetIsStarVisibile(DependencyObject attachingElement, bool value) => attachingElement.SetValue(CustomProperties.IsStarVisibileProperty, value);
public static bool GetIsStarVisibile(DependencyObject attachingElement) => (bool)attachingElement.GetValue(CustomProperties.IsStarVisibileProperty);
#endregion
}
MainWindow.xaml
<Window>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid Name="Server1StateGrid"
CustomProperties.IsStarVisibile="False">
<TextBlock Text=""
Visibility="{Binding ElementName=Server1StateGrid,
Path=(CustomProperties.IsStarVisibile),
Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
</Window>
I have the following class:
public class Person:DependencyObject
{
public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(String), typeof(Person));
public string Name
{
get
{
string result = (string)GetValue(NameProperty);
return result;
}
set
{
SetValue(NameProperty, value);
}
}
}
And the following Window:
<Window x:Class="BindingSelf.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>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding Name}"></TextBox>
</Grid>
</Window>
The code behind for Window is:
public partial class MainWindow : Window
{
Person p = null;
public MainWindow()
{
InitializeComponent();
p = new Person();
p.Name = "Test1";
this.DataContext = p;
}
}
TextBox is bound to Name and it's value ("Test1") correctly shows when I run the application. Now here's my question, if I set a break point in the Get part of the Name property it is completely ignored. I've done a few tests and even If I return empty "Test1" still shows, could somebody explain what's happening?
Thanks
Explanation from MSDN link is self explanatory:
The current WPF implementation of its XAML processor is inherently
dependency property aware. The WPF XAML processor uses property system
methods for dependency properties when loading binary XAML and
processing attributes that are dependency properties. This effectively
bypasses the property wrappers. When you implement custom dependency
properties, you must account for this behavior and should avoid
placing any other code in your property wrapper other than the
property system methods GetValue and SetValue.