WPF: DependencyProperty works on TextBlock but not on custom control - c#

Edit: a sample project can be found here.
I am using a ListBox inside my main window, which I later bind to an ObservableCollection. I use both a TextBlock and a custom control which I bind to the same property of the collection. My problem is that the TextBlock gets properly updated, whereas the custom control doesn’t (it gets default constructed but its Text property is never updated by the binding).
<ListBox Name="MyCustomItemList">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding ItemText}"/>
<local:MyCustomBlock Text="{Binding ItemText}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I implemented MyCustomBlock as a child of System.Windows.Controls.Canvas with a Text dependency property:
public class MyCustomBlock : Canvas
{
public MyCustomBlock() => Text = "<default>";
public MyCustomBlock(string text) => Text = text;
private static void TextChangedCallback(DependencyObject o,
DependencyPropertyChangedEventArgs e)
{
...
}
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
nameof(Text), typeof(string), typeof(MyCustomBlock),
new FrameworkPropertyMetadata("", TextChangedCallback));
}
Finally, this is the data I bind to the ListBox in the MainWindow constructor:
public class MyCustomItem
{
public MyCustomItem(string text) => ItemText = text;
public string ItemText { get; set; }
}
public MainWindow()
{
InitializeComponent();
var list = new ObservableCollection<MyCustomItem>();
list.Add(new MyCustomItem("Hello"));
list.Add(new MyCustomItem("World"));
MyCustomItemList.ItemsSource = list;
}
Did I forget something in my setup? How come TextBlock.Text is seemingly properly updated but not MyCustomBlock.Text?

Dependency properties can get their value from several sources and so WPF employs a precedence system to determine which value applies. "Local" values (provided using SetValue or SetBinding) will override anything provided by the creating template.
In your case, your setting a "local" value in the constructor (presumably intending it to behave as a default value). A better way to set a default value is by providing it in the PropertyMetadata.
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
nameof(Text), typeof(string), typeof(MyCustomBlock),
new FrameworkPropertyMetadata("<default>", TextChangedCallback));

Related

ListBox emtpy when bound to populated ICollectionView

I'm trying to implement filtering on a UserControl (which is essentially just a ListBox with a data template) using ICollectionView.
When I bind to the ICollectionView my LOAListBox is empty.
My xaml looks like this:
<TextBox Text="{Binding SearchString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<!-- LOA_List is a DependencyProperty which binds to ListBox.ItemsSource -->
<controls:LOAListBox LOA_List="{Binding FilteredView, Mode=OneWay}" />
And in my view model, I do this:
public class LOAViewModel : ViewModelBase
{
public ICollectionView FilteredView { get; private set; }
private string _searchString;
public string SearchString
{
get => _searchString;
set
{
_searchString = value;
RaisePropertyChanged("SearchString");
FilteredView.Refresh();
}
}
private List<LOA> _available_LOAs;
public List<LOA> Available_LOAs
{
get => _available_LOAs;
set
{
_available_LOAs = value;
RaisePropertyChanged("Available_LOAs");
}
}
public LOAViewModel()
{
Available_LOAs = data.GetLOAData();
FilteredView = CollectionViewSource.GetDefaultView(Available_LOAs);
FilteredView.Filter = new Predicate<object>(o => Filter(o as LOA));
}
private bool Filter(LOA loa)
{
return SearchString == null || loa.Display_Name.Contains(SearchString);
}
}
During debugging I can see that Available_LOAs is not empty and after GetDefaultView FilteredView also has that same collection. There aren't any binding errors. I also made by filter method always return true just to remove that possibility.
I feel like I must be missing a step but I've checked various other online examples and I can't find anything... My hunch is that it's related to the fact I'm binding to a ListBox nested in a UserControl, but I don't understand why that would matter when it works if change the binding from FilteredView to Available_LOAs directly.
Update; this is the simplified code for LOAListBox:
XAML:
<UserControl>
<ListBox ItemsSource="{Binding LOA_List, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"/>
</UserControl>
Code-behind:
public partial class LOAListBox : UserControl
{
public static readonly DependencyProperty DataSource = DependencyProperty.Register(nameof(LOA_List), typeof(List<LOA>), typeof(LOAListBox), new PropertyMetadata());
public List<LOA> LOA_List
{
get => (List<LOA>)GetValue(DataSource);
set => SetValue(DataSource, value);
}
}
You cannot bind an ICollectionView to a List<T> property.
Change the type of your dependency property to IEnumerable:
public static readonly DependencyProperty DataSource = DependencyProperty.Register(nameof(LOA_List),
typeof(IEnumerable), typeof(LOAListBox), new PropertyMetadata());
public IEnumerable LOA_List
{
get => (IEnumerable)GetValue(DataSource);
set => SetValue(DataSource, value);
}
As a side note, you should also change the name of the dependency property from "DataSource" to "LOA_ListProperty" (and remove the underscore from both names) to follow the naming convention.
After going step-by-step to reproduce the issue, I eventually realised that I wasn't notifying of changes to FilteredView and, not helping matters, I was changing the ICollectionView source without reassigning the ICollectionView,
So I made my FilteredView a standard property that calls RaisePropertyChanged():
private ICollectionView _filteredView;
public ICollectionView FilteredView
{
get => _filteredView;
set
{
_filteredView = value;
RaisePropertyChanged("FilteredView");
}
}
And when I change the ICollectionView source variable I reassign based on the new source collection:
FilteredView = CollectionViewSource.GetDefaultView(Available_Destination_LOAs);
FilteredView.Filter = new Predicate<object>(o => Filter(o as LOA));

Image resource as source for Image not displaying

I defined a custom loading spinner UserControl in a WPF UserContol library.
It has one dependency property:
public string SpinnerSourcePath { get => _spinner.Source.ToString(); set => _spinner.Source = (ImageSource)new ImageSourceConverter().ConvertFromString(value); }
public static readonly DependencyProperty SpinnerSourcePathProperty =
DependencyProperty.Register(nameof(SpinnerSourcePath), typeof(string), typeof(Spinner));
where _spinner is the Image.
(I tried it directly with ImageSource class but no dice)
The xaml looks like this:
<Image x:Name="_spinner" RenderTransformOrigin="0.5 0.5">
<SomeStyleToMakeItRotate.../>
</Image>
and I use it by defining it like:
<c:Spinner SpinnerSourcePath="/Test;component/_Resources/loading.png"/>
(The project name is Test, the Spinner control resides in a different project), nothing is displayed.
However, if I add the Source property directly in the Spinner definition:
<Image x:Name="_spinner" Source="/Test;component/_Resources/loading.png" RenderTransformOrigin="0.5 0.5">
<SomeStyleToMakeItRotate.../>
</Image>
it shows correctly...
This leads me to believe that the dependency property is wrong, but how ?
E1:
After trying to do the same steps on a different control it stopped working again.
This time I have a DP:
public static readonly DependencyProperty ValidationFunctionProperty =
DependencyProperty.Register(nameof(ValidationFunction), typeof(Func<string, bool>), typeof(ValidatedTextBox), new PropertyMetadata(OnAssignValidation));
public Func<string, bool> ValidationFunction {
get => (Func<string, bool>)GetValue(ValidationFunctionProperty);
set => SetValue(ValidationFunctionProperty, value);
}
private static void OnAssignValidation(DependencyObject d, DependencyPropertyChangedEventArgs e) {
Debugger.Break();
}
Control usage:
<c:ValidatedTextBox x:Name="valid"
Text="Test"
ValidationFunction="{Binding Validation, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource test}}"/>
The converter is just a Debugger.Break() and return original
And finally the RelativeSource control is my MainWindow
public MainWindow() {
InitializeComponent();
}
public Func<string,bool> Validation => (s) => true;
(There is a problem with the Text DP as well, but I think I can solve that one on my own)
E2
Ok Pro problem was the RelativePath pointing to UserControl but it was placed in a Window
Your dependency property declaration is wrong, because the get/set methods of the CLR property wrapper must call the GetValue and SetValue methods of the DependencyObject base class (and nothing else).
Besides that, the property should also use ImageSource as its type:
public static readonly DependencyProperty SpinnerSourceProperty =
DependencyProperty.Register(
nameof(SpinnerSource), typeof(ImageSource), typeof(Spinner));
public ImageSource SpinnerSource
{
get { return (ImageSource)GetValue(SpinnerSourceProperty); }
set { SetValue(SpinnerSourceProperty, value); }
}
The Image element in the UserControl's XAML would use the property like this:
<Image Source="{Binding SpinnerSource,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>

error on accessing a property of a control in a wpf user control

i have created a wpf user control with a text box and a combo box.
for accessing the text property of the text box i have used the below code
public static readonly DependencyProperty TextBoxTextP = DependencyProperty.Register(
"TextBoxText", typeof(string), typeof(TextBoxUnitConvertor));
public string TextBoxText
{
get { return txtValue.Text; }
set { txtValue.Text = value; }
}
in another project i have used the control and bind the text as below:
<textboxunitconvertor:TextBoxUnitConvertor Name="wDValueControl" TextBoxText="{Binding _FlClass.SWa_SC.Value , RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Width="161" Height="28" HorizontalAlignment="Left" VerticalAlignment="Top"/>
i am certain that the class that is used for binding is properly working because when i used it to bing with a text box directly in my project it works properly but when i bind it to the text property of textbox in usercontrol it brings null and the binding does not work.
can any one help me?
Your dependency property declaration is wrong. It has to look like shown below, where the getter and setter of the CLR property wrapper call the GetValue and SetValue methods:
public static readonly DependencyProperty TextBoxTextProperty =
DependencyProperty.Register(
"TextBoxText", typeof(string), typeof(TextBoxUnitConvertor));
public string TextBoxText
{
get { return (string)GetValue(TextBoxTextProperty); }
set { SetValue(TextBoxTextProperty, value); }
}
In the XAML of your UserControl, you would bind to the property like this:
<TextBox Text="{Binding TextBoxText,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
If you need to get notified whenever the TextBoxText property changes, you could register a PropertyChangedCallback with PropertyMetadata passed to the Register method:
public static readonly DependencyProperty TextBoxTextProperty =
DependencyProperty.Register(
"TextBoxText", typeof(string), typeof(TextBoxUnitConvertor),
new PropertyMetadata(TextBoxTextPropertyChanged));
private static void TextBoxTextPropertyChanged(
DependencyObject o, DependencyPropertyChangedEventArgs e)
{
TextBoxUnitConvertor t = (TextBoxUnitConvertor)o;
t.CurrentValue = ...
}
You are not creating the dependency property right. Use this code:
public string TextBoxText
{
get { return (string)GetValue(TextBoxTextProperty); }
set { SetValue(TextBoxTextProperty, value); }
}
public static readonly DependencyProperty TextBoxTextProperty =
DependencyProperty.Register("TextBoxText", typeof(string), typeof(TextBoxUnitConvertor), new PropertyMetadata(""));
Then in your custom control Bind the TextBoxText to the value of txtValue.Text

WPF Binding to Custom Property Not Working

I have this control to display a list of usercontrols
<ItemsControl x:Name="LayersList" Margin="10,284,124,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<NaturalGroundingPlayer:LayerControl Item="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The LayerControl control contains this code
public partial class LayerControl : UserControl {
public LayerItem Item { get; set; }
public static readonly DependencyProperty ItemProperty =
DependencyProperty.Register(
"Item",
typeof(LayerItem),
typeof(LayerControl),
new PropertyMetadata(null));
public LayerControl() {
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e) {
// This doesn't work because Item remains null
MainWindow.Instance.LayersList.Items.Remove(Item);
}
}
LayerItem contains this
[PropertyChanged.ImplementPropertyChanged]
public class LayerItem {
public LayerType Type { get; set; }
public string FileName { get; set; }
}
public enum LayerType {
Audio,
Video,
Image
}
Problem is: The Binding is setting the Item property to null. If I change the binding to {Binding Type} instead of {Binding} (and adapt the property type accordingly), then it works. But I can't find a way to bind the whole object. What am I doing wrong?
On a side note, I tried setting ItemsControl.ItemsSource to a ObservableCollection<LayerItem> but that didn't seem to work. Adding items directly to ItemsControl.Items is working. Any idea why that is?
You have incorrectly implemented a dependency property. You should use GetValue and SetValue methods instead of creating an auto-property.
public static readonly DependencyProperty ItemProperty =
DependencyProperty.Register(
"Item", typeof(LayerItem), typeof(LayerControl));
public LayerItem Item
{
get { return (LayerItem)GetValue(ItemProperty); }
set { SetValue(ItemProperty, value); }
}
P.S. You shouldn't access controls like this: MainWindow.Instance.LayersList.Items.Remove(Item). You should use MVVM instead. I'm also not convinced this property is required at all. DataContext may be enough.

Execute a function within DependencyProperty

,When I update an item in observableCollection "MyCollection" I want my custom TextBlock ( to execute function and modify its text. I think I should call function OnMYDataChanged:
<ListBox ItemsSource="{Binding MyCollection}" ItemTemplate="{StaticResource MyTemplate}" >
<DataTemplate x:Key="MyTemplate" >
<Grid >...
<local:MyTextBlock Path="{Binding MyText}" />
where
public class MyTextBlock : TextBlock
{
public string Path
{ get {return (string)GetValue(PathProperty);}
set { SetValue(PathProperty, value); }
}
public static readonly DependencyProperty PathProperty =
DependencyProperty.Register("Path", typeof(string), typeof(MyTextBlock), new PropertyMetadata(OnMyDataChanged));
static void OnMyDataChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {
Text = DoSomethingWithText(); //Does not work
}
When I change one item, OnMyDataChanged gets called, but
I get error there:
An object reference is required for the non-static field, method, or property
To add logic to the execution of a DependencyProperty, you can define a DependencyPropertyDescriptor for each DependencyProperty and add an AddValueChanged call with the necessary logic to it in your custom class's constructor. If you have a custom Grid class called DefinableGrid with a Columns property, the result is then (using C# 6's null-conditional operator ?.):
public int Columns
{
get { return (int) GetValue(ColumnsDependencyProperty); }
set { SetValue(ColumnsDependencyProperty, value); }
}
public static readonly DependencyProperty ColumnsDependencyProperty =
DependencyProperty.Register(nameof(Columns), typeof(int), typeof(DefinableGrid), new PropertyMetadata(0));
DependencyPropertyDescriptor ColumnsPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ColumnsDependencyProperty, typeof(DefinableGrid));
public GridEx()
{
ColumnsPropertyDescriptor?.AddValueChanged(this, delegate
{
ColumnDefinitions.Clear();
for (int i = 0; i < Columns; i++)
ColumnDefinitions.Add(new ColumnDefinition());
});
}
The text property is not acessible because the callback function is static.
You need to cast the obj parameter to 'MyTextBlock', and throug that pointer you can access your object's properties.
Your source object needs to implement INotifyPropertyChanged for this to work (the object with the "MyText" property).
There is a great example implementation on MSDN.
As an aside, your datatemplate can be contained within the ListBox instead of being a static resource (might be less confusing if this is the only spot you want to use that data template):
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You want to use ObservableCollection.CollectionChanged event in this case

Categories