WPF Binding to Custom Property Not Working - c#

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.

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));

Passing ModelData (context) between UserControls (views) MVVM Prism

I'm woking on a project and I have three ViewModels: ObjectDetailsViewMode has a Context (property linking to a model) of type ObjectBase; PropertyTextViewModel has a Context of type PropertyText and PropertyNumberViewModel has a Context of type PropertyNumber.
Below is the structure of the Models:
public class ObjectBase : ModelBase
{
private string _name;
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
public DataCollection<PropertyBase> Properties { get; } = new DataCollection<PropertyBase>();
}
public class PropertyText : PropertyBase
{
private string _default;
public string Default
{
get { return _default; }
set { SetProperty(ref _default, value); }
}
}
public class PropertyNumber : PropertyBase
{
private double _default = 0;
public double Default
{
get { return _default; }
set { SetProperty(ref _default, value); }
}
private double _minValue = 0;
public double MinValue
{
get { return _minValue; }
set { SetProperty(ref _minValue, value); }
}
private double _maxValue = 0;
public double MaxValue
{
get { return _maxValue; }
set { SetProperty(ref _maxValue, value); }
}
}
Regarding the views I have one for each ViewModel. The ObjectDetailsView is a use control that has a TextBox for editing the Object.Name, two buttons to add new PropertyText/PropertyNumber to the Object.Properties and an ItemsControl connected to that Object.Properties.
Each PropertyBase in the ItemsControl (ItemsSource) is resolved into a new view using the DataTemplate marker:
<ItemsControl ItemsSource="{Binding Object.Properties}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type models:PropertyText}">
<views:PropertyTextView />
</DataTemplate>
<DataTemplate DataType="{x:Type models:PropertyNumber}">
<views:PropertyNumberView />
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
As I'm using PRISM the correct ViewModel is automatically created for me and the view DataContext is then set to the new ViewModel. My problem is I need to pass the new Property from the Object.Properties list to the newly created View's ViewModel and store it in the Context property I have there.
I can't avoid creating a View/ViewModel for each property type because there is some under-the-hood logic on some Property types (not the ones I described here.. but I have other types like Boolean, Reference, Enum...)
So I really need to pass a value to the ViewModel I tried to use
<ItemsControl ItemsSource="{Binding Object.Properties}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type models:PropertyText}">
<views:PropertyTextView Context="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type models:PropertyNumber}">
<views:PropertyNumberView Context="{Binding}"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
Be aware that Context is a custom property I created inside the ViewModel's to store the ModelContext. I even created a DependencyProperty in the View's behind code:
public PropertyBase Context
{
get { return (PropertyBase)GetValue(ContextProperty); }
set { SetValue(ContextProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ContextProperty =
DependencyProperty.Register("Context", typeof(PropertyBase), typeof(PropertyTextView), new PropertyMetadata(null));
But it doesn't get linked to the ViewModels set event (I made a break point there and... nothing). I even tried a SetBinding in the PropertyTextView code-behind (constructor):
string propertyInViewModel = "Context";
var bindingViewMode = new Binding(propertyInViewModel) { Mode = BindingMode.TwoWay };
this.SetBinding(ContextProperty, bindingViewMode);
No luck with any of these... I' really stuck.
Something More Simple
If the PropertyTextView has this dependency property.
public string Context
{
get { return (PropertyBase)GetValue(ContextProperty); }
set { SetValue(ContextProperty, value); }
}
// Using a DependencyProperty as the backing store for Context. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ContextProperty =
DependencyProperty.Register("Context", typeof(string), typeof(PropertyTextBuilderView), new PropertyMetadata(null));
I should be able to do:
right?! Why isn't the public property "Context" not being called (I placed a breakpoint there and I get nothing).
Instead of just setting the Context Property of your View to a new Binding you need to assign the Current DataContext like so:
<views:PropertyNumberView Context="{Binding .}"/>
This should assign the Current Views.DataContext Property to your new View.
If you're in an DataTemplate you probably need to specify the RelativeSource:
<views:PropertyNumberView Context="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType=UserControl}}
<ItemsControl ItemsSource="{Binding Object.Properties}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type models:PropertyText}">
<views:PropertyTextView Context="{Binding .}"/>
</DataTemplate>
<ItemsControl.Resources>
</ItemsControl>
As I'm using PRISM the correct ViewModel is automatically created for me
You don't have to use view-first with Prism. The ViewModelLocator is there to help, if you chose to, but view model-first is possible, too.
If I understand you correctly, you have a view model and want to populate a list with child view models. So do just that:
internal class ParentViewModel : BindableBase
{
public ParentViewModel( ParentModel parentModel, IChildViewModelFactory factory )
{
Children = new object[] { factory.CreateTextViewModel(parentModel.TextProperty), factory.CreateNumberViewModel(parentModel.NumberProperty) };
}
public IEnumerable Children { get; }
}
and map the different child view models to child views via DataTemplates.
parentModel.WhateverProperty will have a Name and Value properties as well as setter for the value, probably...

WPF: DependencyProperty works on TextBlock but not on custom control

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));

Usercontrol as datatemplate with bindings

I have an ItemsControl with ItemsSource binded to a list of SystemModels. It has to generate an usercontrol for every system in the list. And in those usercontrols it has some textboxes that show the name, is and location of the system.
My code creates the usercontrols but doesn't fill the textboxes in the usercontrol.
View:
<UserControl x:Name="SystemListScreen">
<ScrollViewer Grid.Row="1">
<ItemsControl x:Name="SystemList" ItemsSource="{Binding Path=Systems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="4"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="SystemModel">
<Widgets:MultiLineButton partID="{Binding ID}"
company="{Binding ItemsSource.Company}"
typeSorter="{Binding ItemsSource.Name, ElementName=SystemList}"
typeLocation="{Binding ItemsSource.Location, ElementName=SystemList}"
buttonCommand="{Binding DataContext.navigateInspectList, ElementName=SystemListScreen}"
buttonCommandParameter="{Binding ItemsSource.ID, ElementName=SystemList}"/>
<!--<Button Content="{Binding ID}"/>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</UserControl>
ViewModel:
private List<SystemModel> systems;
public SystemListViewModel()
{
Systems = new List<SystemModel>();
Systems = SystemAPI.Instance.GetSystems();
}
public string App { get; set; }
public List<SystemModel> Systems
{
get { return systems; }
private set
{
systems = value;
NotifyChanged("systems");
NotifyChanged("NoResultsFound");
}
}
multilinebutton code:
public static readonly DependencyProperty buttonCommandProperty = DependencyProperty.Register("buttonCommand", typeof(ICommand), typeof(MultiLineButton));
public static readonly DependencyProperty buttonCommandParameterProperty = DependencyProperty.Register("buttonCommandParameter", typeof(Object), typeof(MultiLineButton));
public static readonly DependencyProperty partIDProperty = DependencyProperty.Register("partID", typeof(String), typeof(MultiLineButton));
public static readonly DependencyProperty companyProperty = DependencyProperty.Register("company", typeof(String), typeof(MultiLineButton));
public static readonly DependencyProperty typeSorterProperty = DependencyProperty.Register("typeSorter", typeof(String), typeof(MultiLineButton));
public static readonly DependencyProperty typeLocationProperty = DependencyProperty.Register("typeLocation", typeof(String), typeof(MultiLineButton));
public MultiLineButton()
{
this.DataContext = this;
InitializeComponent();
}
public String partID
{
get { return (String)GetValue(partIDProperty); }
set { SetValue(partIDProperty, value); }
}
public String company
{
get { return (String)GetValue(companyProperty); }
set { SetValue(companyProperty, value); }
}
public String typeSorter
{
get { return (String)GetValue(typeSorterProperty); }
set { SetValue(typeSorterProperty, value); }
}
public String typeLocation
{
get { return (String)GetValue(typeLocationProperty); }
set { SetValue(typeLocationProperty, value); }
}
public ICommand buttonCommand
{
get { return (ICommand)GetValue(buttonCommandProperty); }
set { SetValue(buttonCommandProperty, value); }
}
public Object buttonCommandParameter
{
get { return (Object)GetValue(buttonCommandParameterProperty); }
set { SetValue(buttonCommandParameterProperty, value); }
}
what not works: partID="{Binding ID}", company="{Binding ItemsSource.Company}", typeSorter="{Binding ItemsSource.Name, ElementName=SystemList}", typeLocation="{Binding ItemsSource.Location, ElementName=SystemList}" and buttonCommandParameter="{Binding ItemsSource.ID, ElementName=SystemList}".
But if i use just a button as datatemplate with Content="{Binding ID}" It works Perfect, an d if i use the usercontrol outside the datatemplate it works also. But it will not work inside the datatemplate.
The error i get is this: "BindingExpression path error: 'Company' property not found on 'object' ''MultiLineButton' (Name='')'. BindingExpression:Path=Company; DataItem='MultiLineButton' (Name=''); target element is 'MultiLineButton' (Name=''); target property is 'company' (type 'String')"
How can i fix those bindings?
not sure, maybe you should remove DataType="SystemModel" from DateTemplate.
Or try to use simple textbox(Binding id) as DataTemplate, see if there is still empty.
If above didn't get you any help, try Snoop(snoopwpf.codeplex.com) see what happended.
try ObservableCollection:
private ObservableCollection<SystemModel> _systems = new ObservableCollection<SystemModel>();
public ObservableCollection<SystemModel> Systems { get { return _systems; } }
public SystemListViewModel()
{
var systems = SystemAPI.Instance.GetSystems();
foreach (var system in systems)
{
Systems.Add(system);
}
}
and xaml should be:
<UserControl x:Name="SystemListScreen">
<ScrollViewer Grid.Row="1">
<ItemsControl x:Name="SystemList" ItemsSource="{Binding Path=Systems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="4"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Widgets:MultiLineButton
partID="{Binding ID}"
company="{Binding Company}"
typeSorter="{Binding Name}"
typeLocation="{Binding Location}"
buttonCommand="{Binding DataContext.navigateInspectList,
ElementName=SystemListScreen}"
buttonCommandParameter="{Binding ItemsSource.ID,
ElementName=SystemList}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</UserControl>
As chao wang said:
remove DataType="SystemModel" because if you're using just one type of DataType as DataTemplate it's not necessary. and the correct syntax is DataType="vm:SystemModel" where vm is defined in a parent tag like : xmlns:vm="clr-namespace:MySolution.MyVmProject.MyFolder"
also, check these:
remove ItemsSource. from Bindings inside DataTemplate because it's just wrong.
double check all names in bindings because if they're wrong, a null value is considered during runtime and you never know.
check your dataContext make sure UserControl have its DataContext to the correct instance of type dependency object which has Systems in it. and make sure it remains that way.

Binding User Control Property with another Property

In my Windows Store Application I want to bind a Property in a user control with another Property in logical class
the User Control "Card_UC.xaml.cs" contains this Property:
public string Card_ID
{
get { return (string)GetValue(Card_ID_Property); }
set { SetValue(Card_ID_Property, value); }
}
public DependencyProperty Card_ID_Property =
DependencyProperty.Register(
"Card_ID",
typeof(string),
typeof(Card_UC),
new PropertyMetadata(null));
and in my logical class "Card_Data.cs":
public string Card_ID { get; set; }
In Main Page I want to make a Grid of this Cards using data binding like this
<GridView
x:Name="UI_GView_Cards"
ItemsSource="{Binding}">
<GridView.ItemTemplate>
<DataTemplate>
<local:CardControl
x:Name="UC_Card"
CardPressed="CardControl_CardPressed"
ID="{Binding Path=Card_ID, ElementName=Card_UC, Mode=TwoWay}"/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
all the other Properties binding in the "Card_UC.xaml" working except Card_ID
the problem now is that the application crashes every time I access the ID Property using
return (string)GetValue(Card_ID_Property);
Error: "Object reference not set to an instance of an object."
Problem Fixed:
the problem in this line:
ID="{Binding Path=Card_ID, ElementName=Card_UC, Mode=TwoWay}"
changed to:
ID="{Binding Card_ID}"
Edit:
Fixed "Copy/Paste" mistake.
Re-Format the question.
It looks like you have a cut and paste error:
public string Card_ID
{
get { return (string)GetValue(UCAppsProperty); }
set { SetValue(UCAppsProperty, value); }
}
to:
public string Card_ID
{
get { return (string)GetValue(Card_ID_Property); }
set { SetValue(Card_ID_Property, value); }
}

Categories