Dependency Property does not work after view model was initialized - c#

I would like to write a custom asynchronous image container custom control. I have created a list from this control:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<custom:CustomImage Width="64" Height="64" BaseUri="{Binding Uri}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The Items property is a list of object A what I initialize in the MainWindowViewModel:
public List<A> Items { get; set; } = new List<A>();
and
foreach (XmlNode item in doc.LastChild.FirstChild.SelectNodes(".//item"))
{
Items.Add(
new A
{
Title = item.FirstChild.InnerText,
Uri = new Uri(item.SelectNodes(".//enclosure")[0].Attributes["url"].Value)
}
);
}
I want to set a Dependency Property on the custom control (you can see above: BaseUri="{Binding Uri}". Uri is a property of the class A.
This is the DP implementation:
public Uri BaseUri
{
get { return (Uri)GetValue(BaseUriProperty); }
set { SetValue(BaseUriProperty, value); }
}
public static readonly DependencyProperty BaseUriProperty =
DependencyProperty.Register("BaseUri", typeof(Uri),
typeof(CustomImage), new FrameworkPropertyMetadata(default(Uri), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
It works only if the CustomImage custom control doesn't have any view model. If I do this in the CustomImage's constructor:
DataContext = new CustomImageViewModel();
it doesn't work anymore.
Any idea?

You should never explicitly set the DataContext of a UserControl. Doing so effectively prevents that the DataContext is inherited from the control's parent, as it is required by a binding like
BaseUri="{Binding Uri}"
So, remove the line
DataContext = new CustomImageViewModel();
from the constructor of your control.
It is not true that the "control doesn't have any view model" when you don't set it explicitly. In fact, the view model (or the inherited DataContext) is set - via the ItemsControl's item container - to the appropriate item from the ItemsSource collection. So the DataContext of your control is automatically set to an instance of your class A.

Related

How to write getters and setters to convert WPF dependency property? [duplicate]

I am trying (and failing) to do data binding on a dependency property in xaml. It works just fine when I use code behind, but not in xaml.
The user control is simply a TextBlock that bind to the dependency property:
<UserControl x:Class="WpfTest.MyControl" [...]>
<TextBlock Text="{Binding Test}" />
</UserControl>
And the dependency property is a simple string:
public static readonly DependencyProperty TestProperty
= DependencyProperty.Register("Test", typeof(string), typeof(MyControl), new PropertyMetadata("DEFAULT"));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
I have a regular property with the usual implementation of INotifyPropertyChanged in the main window.
private string _myText = "default";
public string MyText
{
get { return _myText; }
set { _myText = value; NotifyPropertyChanged(); }
}
So far so good. If I bind this property to a TextBlock on the main window everything works just fine. The text update properly if the MyText changes and all is well in the world.
<TextBlock Text="{Binding MyText}" />
However, if I do the same thing on my user control, nothing happens.
<local:MyControl x:Name="TheControl" Test="{Binding MyText}" />
And now the fun part is that if I do the very same binding in code behind it works!
TheControl.SetBinding(MyControl.TestProperty, new Binding
{
Source = DataContext,
Path = new PropertyPath("MyText"),
Mode = BindingMode.TwoWay
});
Why is it not working in xaml?
The dependency property declaration must look like this:
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register(
nameof(Test),
typeof(string),
typeof(MyControl),
new PropertyMetadata("DEFAULT"));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
The binding in the UserControl's XAML must set the control instance as the source object, e.g. by setting the Bindings's RelativeSource property:
<UserControl x:Class="WpfTest.MyControl" ...>
<TextBlock Text="{Binding Test,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</UserControl>
Also very important, never set the DataContext of a UserControl in its constructor. I'm sure there is something like
DataContext = this;
Remove it, as it effectively prevents inheriting a DataContext from the UserConrol's parent.
By setting Source = DataContext in the Binding in code behind you are explicitly setting a binding source, while in
<local:MyControl Test="{Binding MyText}" />
the binding source implicitly is the current DataContext. However, that DataContext has been set by the assignment in the UserControl's constructor to the UserControl itself, and is not the inherited DataContext (i.e. the view model instance) from the window.

WPF User control binding UIElement

I have creted my User Control where I would like to make posibility to bind a UIElement. My user control:
public partial class TextArea : UserControl
{
public UIElement AncestorContainer
{
get => (UIElement)GetValue(AncestorContainerProperty);
set => SetValue(AncestorContainerProperty, value);
}
public TextArea()
{
InitializeComponent();
DataContext = this;
}
public static readonly DependencyProperty AncestorContainerProperty =
DependencyProperty.Register("AncestorContainerProperty", typeof(UIElement), typeof(TextArea), new PropertyMetadata(null));
}
When creating my UserControl in C# it is working fine - no exceptions like this:
var textArea = new TextArea
{
AncestorContainer = Root, // Root is name of Grid
Text = textItem.Text
};
However when trying to use binding in XAML I get an exception:
<ItemsControl ItemsSource="{Binding SuggestedTexts}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<components:TextArea
AncestorContainer="{Binding ElementName=Sidebar}"/> <!-- Side bar is name of Grid above in XAML -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And the exception:
"Binding" cannot be set in the "ParentContainer" type "TextArea".
"Binding" can only be set in the properties of the DependencyProperty
object DependencyObject.
You have a typo declaring dependency property as you have written
DependencyProperty.Register("AncestorContainerProperty", ...
which should be replaced by
DependencyProperty.Register("AncestorContainer", ...
or better
DependencyProperty.Register(nameof(AncestorContainer), ...

How to set a property of a property in XAML?

I have a user-control as my view (named MyView) and it has it's data context set to an instance of my view-model (of type MyViewModel).
I have in my view's code-behind a read-only property for it (which is the MVVM-Light snippet) that looks like so:
public MyViewModel Vm
{
get { return (MyViewModel) DataContext; }
}
MyViewModel has a property named Title of type string, and I want to change it through XAML because MyView is being used as an ItemTemplate for a ListBox.
<ListBox ItemsSource="{Binding MyViewModelCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Controls:MyView /> <!-- How do I set Vm.Title property here? -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
How can I do this?
or perhaps there is a better way?
Could you simply make a property within your MyView that reflects down to the view model?
public string Title
{
get { return ((MyViewModel) DataContext).Title; }
set { ((MyViewModel) DataContext).Title = value; }
}
Then write:
<Controls:MyView Title="MyTitle" />
If you want to bind the title, you'll have to make it a dependency property not a regular property.

Extending WPF ListBoxItem for sourced items not working

If I create a class that extends WPF's ListBoxItem, create a list of these objects, try to bind the list to a ListBox's ItemsSource, the items will not display:
<ListBox ItemsSource="{Binding Path=LbData, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">
<ListBox.ItemTemplate>
<DataTemplate >
<TextBlock Text="{Binding Path=Display}" Margin="1" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public partial class MainWindow : Window
{
public IEnumerable lbData = new List<LbItem>();
public IEnumerable LbData
{
get { return lbData; }
set { lbData = value; }
}
public MainWindow()
{
InitializeComponent();
LbData = new List<LbItem> { new LbItem("a"), new LbItem("b") };
}
}
public class LbItem : ListBoxItem
{
public string Display { get; private set; }
public LbItem(string v)
{
Display = v;
}
}
I'm new to WPF and don't see why this should be an issue. TIA
The ItemsSource normally is used for data-objects, and what you do there (setting the ItemTemplate) suggest that you should not make your object inherit from ListBoxItem at all, instead it should be a normal object (possibly implementing INotifyPropertyChanged if properties may change after creation). If the list changes it should implement INotifyCollectionChanged.
Because the items are already ListBoxItems the DataTemplate you set will be disregarded. There should be the following error in your Visual Studio Output-window:
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='LbItem'
try setting LbData before InitializeComponent(); or Implement INotifyPropertyChanged also you probably would want to bind to an ObservableCollection

WPF ItemsSource works in code-behind but not in XAML

I have a simple combobox with a checkbox inside as such:
<ComboBox Height="23" HorizontalAlignment="Left" Margin="158,180,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120" ItemsSource="{Binding collection}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Name}"></CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The datacontext is simply the code behind, and to test it I use the following code:
public ObservableCollection<Foo> collection { get; set; }
private void button1_Click(object sender, RoutedEventArgs e)
{
collection = new ObservableCollection<Foo>();
this.comboBox1.ItemsSource = collection;
Foo f = new Foo("DSD");
collection.Add(f);
}
When I set the ItemsSource as I have in the code, then it works fine, but I want to set the ItemsSource in the Xaml, however it does not work using the Xaml above. I have also tried setting it to Path = "". Anybody know why?
Thanks
You need to assign DataContext to the control. something like:
var window = new Window1();
window.DataContext = new WindowDC();
window.Show();
where Window1 class contains the combobox, and WindowDC is like:
public class WindowDC
{
public ObservableCollection<Foo> collection { get; set; }
}
That's how this will work.
What you actually do is that you place collection into control class, and set your datacontext for combobox only.
But, for testing purposes, you can still set Combox.Datacontext in control constuctor.
Bindings in WPF always have a Source. If you don't specify the source in the binding itself, then it will implicitly use the DataContext of the control or an ancestor of it. So if you want to bind to properties in your codebehind file, you have to set the DataContext to an object of the class which contains the collection property. In your case this is the instance of the Window (this).
DataContext = this;
As the commentor pointed out, it's not considered good style putting business logic or data inside the code behind file. So consider writing a separate class which contains your collection property and which you can use to initalize your DataContext. If you are writting bigger applications you should take a look at patterns like MVVM, which uses databinding to provide a better separation between your view and your model.
Edit: Changed ordering and incorporated feedback
Make sure there exist a public property collection in your code behind.
in the code behind also do this.DataContext = this
Finally implement INotifyPropertyChanged to tell the view that you have changed the collection once you add items in it
public ObservableCollection<Foo> Collection
{
get
{
return collection;
}
set
{
collection = value;
OnPropertyChanged("Collection");
}
private void button1_Click(object sender, RoutedEventArgs e)
{
collection = new ObservableCollection<Foo>();
//this.comboBox1.ItemsSource = collection;
Foo f = new Foo("DSD");
collection.Add(f);
OnPropertyChanged("Collection");
}
It is working when you are setting combo's item source in code behind because the source of combo is getting updated like wise to set the item source in XAML you have to make a property with INotifyPropertyChanged that keep update the combo's itemsource every time you update your collection via this property..
private ObservableCollection<Foo> _Collection;
public ObservableCollection<Foo> Collection
{
get
{
return collection;
}
set
{
collection = value;
OnPropertyChanged("Collection");
}
Now as you are filling collection on button click you just have to set that collection in the property as..
_Collection = new ObservableCollection<Foo>();
Foo f = new Foo("DSD");
_Collection .Add(f);
Collection = _Collection ; //here property call OnPropertyChange
like wise you can provide data to any control. It is jsut the game of INotifyPropertyChanged property.
Hope this will help you

Categories

Resources