0

I have been trying to set up a two-way binding in wpf. There is a canvas that is populated with ContentControls, each one containing a filled rectangle. Through a thumb, each ContentControl can be made larger and therefore have a changed width.

These ContentControls have been generated by code and live within a class (CanvasElement), which is used for other calculations.

I'd like to set up a two way binding between the ContentControl Property Width and the public double variable Width within the CanvasElement class. When the Thumb is used to change the width of the contentControl, the Width of the CanvasElement is updated, but the other way it doesn't work.

Here is what I have so far:

 public class CanvasElement
{
    private double width;
    public double height;
    private Point location; // This is the upper left point of the rectangle
    public Brush color;
    public string UID;
    public ContentControl canvasElement;

    public CanvasElement(Point location, double width, double height, Brush color, string UID)
    {
        this.location = location;
        this.width = width;
        this.height = height;
        this.color = color;
        this.UID = UID;

        canvasElement = new ContentControl() { Width = this.width, Height = this.height, Uid = UID };
        Canvas.SetLeft(canvasElement, this.location.X);
        Canvas.SetTop(canvasElement, this.location.Y);

        canvasElement.Content = new Rectangle() {
            IsHitTestVisible = false,
            Fill = this.color,
            Stroke =Brushes.LightGray,
            StrokeThickness = 2,
            Margin =  new Thickness(0,5,0,5),
            RadiusX = 10,
            RadiusY = 10};
        addBinding();

    }

    private void addBinding()
    {
        Binding widthBinding = new Binding();
        widthBinding.Source = this;
        widthBinding.Path = new PropertyPath("Width");
        widthBinding.Mode = BindingMode.TwoWay;
        widthBinding.NotifyOnSourceUpdated = true;
        widthBinding.NotifyOnTargetUpdated = true;
        //widthBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
        BindingOperations.SetBinding(canvasElement, ContentControl.WidthProperty, widthBinding);

    }




    public double Width
    {
        get
        {
            return width;
        }
        set
        {
            if(width != value)
            {
                width = value;
                OnPropertyChanged();
            }
        }
    }

As well as:

 public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

I am thankful for every hint I can get!

Thanks for helping out!

timiTao
  • 1,417
  • 3
  • 20
  • 34
Ecuashungo
  • 78
  • 12
  • 2
    A ContentControl member called `canvasElement` in a class called `CanvasElement` makes my brain hurt. Sorry. – Clemens Mar 07 '18 at 13:00
  • I am very new to wpf, so I don't know if this is the right way to achieve this nor if I am using the right elements. My apologies. – Ecuashungo Mar 07 '18 at 13:04
  • It hasn't got to do with WPF. It's just that the names are confusing. – Clemens Mar 07 '18 at 13:05
  • That said, you should consider using an ItemsControl with a Canvas as ItemsPanel and the Rectangle in its ItemTemplate. See this for a start: https://stackoverflow.com/q/22324359/1136211 – Clemens Mar 07 '18 at 13:06
  • I will look into that, thank you! – Ecuashungo Mar 07 '18 at 13:09
  • 1
    @Clemens: Thank you for this hint! After rewriting nearly the whole code the new setup with ItemsControl worked well and all TwoWay bindings are established. – Ecuashungo Mar 08 '18 at 14:34

1 Answers1

0

As Clemens pointed out in his comment the ItemsControl is the right way to do this. As I have different UIElements that are added to the canvas I needed to add an ItemsControl.ItemTemplateSelector as well as an ItemsControlItemContainerStyleSelector.

XAML

<AdornerDecorator ClipToBounds="True">
    <ItemsControl ItemsSource="{Binding CanvasElementList}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas Background="FloralWhite"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyleSelector>
            <local:CustomStyleSelector>
                <local:CustomStyleSelector.CanvasStyle_TL>
                    <Style TargetType="ContentPresenter">
                        <Setter Property="Canvas.Left" Value="{Binding Path=X, Mode=TwoWay}"/>
                        <Setter Property="Canvas.Top" Value="{Binding Path=Y, Mode=TwoWay}"/>
                    </Style>
                </local:CustomStyleSelector.CanvasStyle_TL>
                <local:CustomStyleSelector.CanvasStyle_TR>
                    <Style TargetType="ContentPresenter">
                        <Setter Property="Canvas.Right" Value="{Binding Path=X, Mode=TwoWay}"/>
                        <Setter Property="Canvas.Top" Value="{Binding Path=Y, Mode=TwoWay}"/>
                    </Style>
                </local:CustomStyleSelector.CanvasStyle_TR>
                <local:CustomStyleSelector.CanvasStyle_BL>
                    <Style TargetType="ContentPresenter">
                        <Setter Property="Canvas.Left" Value="{Binding Path=X, Mode=TwoWay}"/>
                        <Setter Property="Canvas.Bottom" Value="{Binding Path=Y, Mode=TwoWay}"/>
                    </Style>
                </local:CustomStyleSelector.CanvasStyle_BL>
                <local:CustomStyleSelector.CanvasStyle_BR>
                    <Style TargetType="ContentPresenter">
                        <Setter Property="Canvas.Right" Value="{Binding Path=X, Mode=TwoWay}"/>
                        <Setter Property="Canvas.Bottom" Value="{Binding Path=Y, Mode=TwoWay}"/>
                    </Style>
                </local:CustomStyleSelector.CanvasStyle_BR>
                <local:CustomStyleSelector.LineStyle>
                    <Style TargetType="ContentPresenter">
                    </Style>
                </local:CustomStyleSelector.LineStyle>
            </local:CustomStyleSelector>
        </ItemsControl.ItemContainerStyleSelector>
        <ItemsControl.ItemTemplateSelector>
            <local:CustomTemplateSelectors>
                <local:CustomTemplateSelectors.LabelTemplate>
                    <DataTemplate>
                        <TextBlock
                                Text="{Binding Text}"
                                FontWeight="{Binding FontWeight}"
                                FontSize="{Binding FontSize}"/>
                    </DataTemplate>
                </local:CustomTemplateSelectors.LabelTemplate>
                <local:CustomTemplateSelectors.LineTemplate>
                    <DataTemplate>
                        <Line X1="{Binding X1}"
                                    X2="{Binding X2}"
                                    Y1="{Binding Y1}"
                                    Y2="{Binding Y2}"
                                    Stroke="{Binding Stroke}"
                                    StrokeThickness="{Binding StrokeThickness}"
                                    StrokeDashArray="{Binding StrokeDashArray}"/>
                    </DataTemplate>
                </local:CustomTemplateSelectors.LineTemplate>
                <local:CustomTemplateSelectors.CanvasElementTemplate>
                    <DataTemplate>
                        <ContentControl Width="{Binding Path=Width, Mode=TwoWay}" Height="{Binding Path=Height, Mode=TwoWay}" 
                                    Style="{StaticResource ResourceKey=DesignerItemStyle}"
                                    MouseDoubleClick="ContentControl_MouseDoubleClick">
                            <Rectangle Fill="{Binding Color}"
                                Stroke="LightGray"
                                StrokeThickness="2"
                                Margin="0,5,0,5"
                                RadiusX="10"
                                RadiusY="10"
                                IsHitTestVisible="False"/>
                        </ContentControl>
                    </DataTemplate>
                </local:CustomTemplateSelectors.CanvasElementTemplate>

            </local:CustomTemplateSelectors>

        </ItemsControl.ItemTemplateSelector>
    </ItemsControl>
</AdornerDecorator>

Code

In the corresponding .cs file there are these ObservableCollections and the CompositeCollection. The later is the element that binds to the ItemsControl. To add new elements you have to add elements to the Observable Collections

CanvasElementList4Canvas = new ObservableCollection<CanvasElement>();
LineList4Canvas = new ObservableCollection<CustomLine>();
LabelList4Canvas = new ObservableCollection<LabelTextBlock>();

CanvasElementList = new CompositeCollection();
CanvasElementList.Add(new CollectionContainer() { Collection = CanvasElementList4Canvas });
CanvasElementList.Add(new CollectionContainer() { Collection = LineList4Canvas });
CanvasElementList.Add(new CollectionContainer() { Collection = LabelList4Canvas });

To set up the binding the CustomLine Class is shown here. The CanvasElement and the LabelTextBlock class are set up in the same way.

CustomLine

public class CustomLine : INotifyPropertyChanged
{
    private double _X1;
    private double _X2;
    private double _Y1;
    private double _Y2;
    private int _strokeThickness = 3;
    private Brush _stroke = Brushes.Black;
    private DoubleCollection _strokeDashArray = new DoubleCollection() {  1.0, 0.0  };

    public double X1 { get { return _X1; } set { if (_X1 != value) { _X1 = value; NotifyPropertyChanged("X1"); } } }
    public double X2 { get { return _X2; } set { if (_X2 != value) { _X2 = value; NotifyPropertyChanged("X2"); } } }
    public double Y1 { get { return _Y1; } set { if (_Y1 != value) { _Y1 = value; NotifyPropertyChanged("Y1"); } } }
    public double Y2 { get { return _Y2; } set { if (_Y2 != value) { _Y2 = value; NotifyPropertyChanged("Y2"); } } }
    public int StrokeThickness { get { return _strokeThickness; } set { if (_strokeThickness != value) { _strokeThickness = value; NotifyPropertyChanged("StrokeThickness"); } } }
    public Brush Stroke { get { return _stroke; } set { if (_stroke != value) { _stroke = value; NotifyPropertyChanged("Stroke"); } } }
    public DoubleCollection StrokeDashArray { get { return _strokeDashArray; } set { if (_strokeDashArray != value) { _strokeDashArray = value; NotifyPropertyChanged("StrokeDashArray"); } } }



    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(String propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

and finally the custom selectors are needed to use the right template and the right style for within the canvas:

public class CustomTemplateSelectors : DataTemplateSelector
{
    public DataTemplate CanvasElementTemplate { get; set; }
    public DataTemplate LineTemplate { get; set; }
    public DataTemplate LabelTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item is CanvasElement)
            return CanvasElementTemplate;
        else if (item is CustomLine)
            return LineTemplate;
        else if (item is LabelTextBlock)
            return LabelTemplate;
        else return base.SelectTemplate(item, container);
    }
}

public class CustomStyleSelector : StyleSelector
{
    public Style CanvasStyle_TL { get; set; }
    public Style CanvasStyle_TR { get; set; }
    public Style CanvasStyle_BL { get; set; }
    public Style CanvasStyle_BR { get; set; }
    public Style LineStyle { get; set; }

    public override Style SelectStyle(object item, DependencyObject container)
    {
        if (item is CanvasElement)
            return CanvasStyle_TL;
        else if (item is CustomLine)
            return LineStyle;
        else if (item is LabelTextBlock)
        {
            var tempItem = item as LabelTextBlock;
            if (tempItem.Tag == "TL")
                return CanvasStyle_TL;
            else if (tempItem.Tag == "TR")
                return CanvasStyle_TR;
            else if (tempItem.Tag == "BL")
                return CanvasStyle_BL;
            else if (tempItem.Tag == "BR")
                return CanvasStyle_BR;
            else return base.SelectStyle(item, container);
        }
        else return base.SelectStyle(item, container);
    }
}
Ecuashungo
  • 78
  • 12