Reputation: 61
I am having a problem with binding I am trying to bind a list of items to an items control. I do have it working but I am getting some binding errors that depend on how WidgetVM is defined.
If WidgetVM inherits from thumb (which is what I want), it wont appear on the canvas. the same is for any other derivation of control. Now the three items defined in the constructor are in the Live visual tree at the position but he Canvas.Left and Canvas .Top Property is NaN, which I guess is why they are not showing
Also there are binding errors stating the Position Property was not found on type MainWindow? I cannot figure out why it looks for the values in MainWindow and not WidgetVM. these errors dissapear when I remove the inheritance from Thumb.
If I remove the inheritance from WidgetVM the text values appear as expected on screen so the code to place the items works but not when the object is derived from a control such as thumb.
How can I get this to work when WidgetVM inherits from Thumb as I want to use the drag delta functionality. Thanks.
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=WidgetName}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Path=Position.X}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Position.Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
The Code Behind is as follows
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string v)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
}
private ObservableCollection<Widgetvm> Widgets;
public ObservableCollection<Widgetvm> Widgets
{
get { return Widgets; }
set { Widgets = value; OnPropertyChanged("Widgets"); }
}
public MainWindow()
{
Widgets = new ObservableCollection<Widgetvm>()
{
new Widgetvm (){WidgetName="initial", Position = new Point(30,30) },
new Widgetvm (){WidgetName="inbetween", Position = new Point(120,130) },
new Widgetvm (){WidgetName="final", Position = new Point(330,330) },
};
InitializeComponent();
}
}
The Widgetvm is defined as follows
public class Widgetvm : Thumb, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string v)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
}
private string WidgetName;
public string WidgetName
{
get { return WidgetName; }
set { WidgetName = value; OnPropertyChanged("WidgetName"); }
}
private Point position;
public Point Position
{
get { return position; }
set { position = value; OnPropertyChanged("Position"); }
}
}
Upvotes: 0
Views: 414
Reputation: 29028
This is not how it works. When you add items to the ItemsSource
then the DataTemplate
is rendered and not the data item (Widgetvm
). In other words, in your code only the TextBlock
is displayed and no Thumb
(because Thumb
is the data item).
If you want do show the Thumb
then you must add it to the DataTemplate
. In case you need to implement additional logic for the Thumb
, create a new control named Widget
that extends Thumb
.
ListBox
instead of the ItemsControl
.nameof
instead of strings to use member names as argument.INotifyPropertyChanged
on controls. Impement properties as DependencyProperty
instead. This allows the property to be a binding target or animated. It also improves the perfomance of your application. Generally, use DependecyProperty
for classes that extend DependecyObject
and INotifyPropertyChanged
for all other types.The following example shows how to create the draggable control Widget
, that extends Thumb
and is positioned on a Canvas
:
WidgetItem.cs
The data model.
public class WidgetItem : INotifyPropertyChanged
{
protected virtual void OnPropertyChanged([CallerMembername] string propertyName = "")
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
private Point position;
public Point Position
{
get => this.position;
set
{
this.position = value;
OnPropertyChanged();
}
}
}
Widget.cs
The draggable control.
Create a control library or use a existing one and add the following control. The only important key is that your project has a Themes folder that must contain the Generic.xaml file.
public class Widget : Thumb
{
public string Name
{
get => (string)GetValue(NameProperty);
set => SetValue(NameProperty, value);
}
public static readonly DependencyProperty NameProperty = DependencyProperty.Register(
"Name",
typeof(string),
typeof(Widget),
new PropertyMetadata(default));
static Widget()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(Widget),
new FrameworkPropertyMetadata(typeof(Widget)));
}
// TODO::Implement UI logic and handle Thumb events here
}
The drag part is not implemented, TODO:
X
Y
X
and Y
accordinglyDataTemplate
of the ListBox
, bind the WidgetItem.Position
to X
and Y
Generic.xaml
<Style TargetType="Widget">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Widget">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<!-- Define visual appearance here. For example add a Rectangle and/or
a TextBlock to display the Name -->
<TextBlock Text="{TemplateBinding Name}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow.xaml.cs
Define the data source.
public partial class MainWindow : Window
{
public ObservableCollection<Widgetvm> WidgetItems { get; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.WidgetItems = new ObservableCollection<WidgetItem>
{
new WidgetItem(){ Name="initial", Position = new Point(30,30) },
new WidgetItem(){ Name="inbetween", Position = new Point(120,130) },
new WidgetItem(){ Name="final", Position = new Point(330,330) },
};
}
}
MainWindow.xaml
<Window>
<ListBox ItemsSource="{Binding WidgetItems}">
<ListBox.ItemPanel>
<!-- Set to Canvas -->
<ListBox.ItemPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type WidgetItem}">
<Widget Name="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding Position.X}"/>
<Setter Property="Canvas.Top" Value="{Binding Position.Y}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Window>
Upvotes: 1