سؤال

I have ItemsControl in which the ItemsPanel is a Canvas. The data template is a Canvas wrapped in a Border and it has additional Rectangle in it. This is the ItemsControl XAML:

<ItemsControl x:Name="Items" ItemsSource="{Binding TileResources, ElementName=ResourceWindow}" >
                <ItemsControl.ItemsPanel >
                    <ItemsPanelTemplate>
                        <Canvas Background="SkyBlue" Width="500" Height="500"  IsItemsHost="True" Loaded="Canvas_Loaded_1" MouseDown="Canvas_MouseDown" MouseUp="Canvas_MouseUp" MouseMove="Canvas_MouseMove"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <local:ResourceBorderControl x:Name="ResRect" BorderThickness="2"
                                MouseDown="selectionBox_MouseDown" MouseUp="selectionBox_MouseUp" >
                            <Canvas Width="{Binding Width, ElementName=ResRect}"  Height="{Binding Height, ElementName=ResRect}" Background="White" Opacity="0.1">
                                <Rectangle Stroke="Red" StrokeThickness="2"
                                    Canvas.Left="{Binding CollisionX, ElementName=ResRect}" 
                                    Canvas.Top="{Binding CollisionY, ElementName=ResRect}"
                                    Width="{Binding CollisionWidth, ElementName=ResRect}"
                                    Height="{Binding CollisionHeight, ElementName=ResRect}"
                                    />

                            </Canvas>

                        </local:ResourceBorderControl>


                        <DataTemplate.Triggers>
                            <DataTrigger Binding="{Binding IsSelected}" Value="true">
                                <Setter Property="BorderBrush" Value="Purple" TargetName="ResRect"/>
                            </DataTrigger>

                            <DataTrigger Binding="{Binding IsSelected}" Value="false">
                                <Setter Property="BorderBrush" Value="SeaGreen" TargetName="ResRect"/>
                            </DataTrigger>
                        </DataTemplate.Triggers>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
                <ItemsControl.ItemContainerStyle>
                    <Style>
                        <Setter Property="Canvas.Left" Value="{Binding X}" />
                        <Setter Property="Canvas.Top" Value="{Binding Y}" />
                        <Setter Property="FrameworkElement.Width" Value="{Binding Width}" />
                        <Setter Property="FrameworkElement.Height" Value="{Binding Height}" />
                        <Setter Property="local:ResourceBorderControl.CollisionX" Value="{Binding CollideX}" />
                        <Setter Property="local:ResourceBorderControl.CollisionY" Value="{Binding CollideY}" />
                        <Setter Property="local:ResourceBorderControl.CollisionWidth" Value="{Binding CollideWidth}" />
                        <Setter Property="local:ResourceBorderControl.CollisionHeight" Value="{Binding CollideHeight}" />
                    </Style>
                </ItemsControl.ItemContainerStyle>
            </ItemsControl>

As you can see, I am using custom Border control - ResourceBorderControl. It has 4 attached properties which describe the inner rectangle: CollisionX CollisionY CollisionWidth CollisionHeight

It works fine if I just hardcode the attached properties (CollisionX and so on) in XAML - the Rectangle in Canvas is rendered as I want it to be.

I want it to be set using the style (or any other way if it's possible) because I need it to be filled with values from rendered object from ItemsSource.

And it just doesn't work. I can't even debug it in WPF tree visualizer - when I try to look at ResourceBorderControl object I can't see the Collision values. When I run the program, I can see rendering of Border with Canvas (I can see the opacity) but there isn't anything in it. I suppose these values get lost after being set by Style Setters.

So my question is - am I doing anything wrong here? Or it's just WPF that does not allow setting custom attached values using styles and I should use another approach? If latter, how else can I do it?

Edit: This is the code of class which instances are in ObservableCollection that is binded to ItemsSource

public class ResourceModelBase : IResourceModel
{
    private String _name, _defaultLayer;
    private double _x, _y, _width, _height, _collideX, _collideY, _collideWidth, _collideHeight;
    private bool _isSelected;

    public ResourceModelBase(double x, double y, double width, double height)
    {
        X = x;
        Y = y;
        Width = width;
        Height = height;

        CollideX = 1;
        CollideY = 1;
        CollideWidth = 100;
        CollideHeight = 100; 

        IsSelected = false;

        Name = "RES_" + width.ToString() + height.ToString();
    }



    public void Select()
    {
        IsSelected = true;
    }

    public void Deselect()
    {
        IsSelected = false;
    }


    public new String ToString()
    {
        return Name;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public String Name
    {
        get
        {
            return _name;
        }

        set
        {
            if (value != _name)
            {
                _name = value;
                NotifyPropertyChanged();
            }
        }
    }

    public String DefaultLayer
    {
        get
        {
            return _defaultLayer;
        }

        set
        {
            if (value != _defaultLayer)
            {
                _defaultLayer = value;
                NotifyPropertyChanged();
            }
        }
    }

    public double X
    {
        get
        {
            return _x;
        }

        set
        {
            if (value != _x)
            {
                _x = value;
                NotifyPropertyChanged();
            }
        }
    }

    public double Y
    {
        get
        {
            return _y;
        }

        set
        {
            if (value != _y)
            {
                _y = value;
                NotifyPropertyChanged();
            }
        }
    }

    public double Width
    {
        get
        {
            return _width;
        }

        set
        {
            if (value != _width)
            {
                _width = value;
                NotifyPropertyChanged();
            }
        }
    }

    public double Height
    {
        get
        {
            return _height;
        }

        set
        {
            if (value != _height)
            {
                _height = value;
                NotifyPropertyChanged();
            }
        }
    }

    public bool IsSelected
    {
        get
        {
            return _isSelected;
        }

        set
        {
            if (value != _isSelected)
            {
                _isSelected = value;
                NotifyPropertyChanged();
            }
        }
    }

    public double CollideX
    {
        get
        {
            return _collideX;
        }

        set
        {
            if (value != _collideX)
            {
                _collideX = value;
                NotifyPropertyChanged();
            }
        }
    }

    public double CollideY
    {
        get
        {
            return _collideY;
        }

        set
        {
            if (value != _collideY)
            {
                _collideY = value;
                NotifyPropertyChanged();
            }
        }
    }

    public double CollideWidth
    {
        get
        {
            return _collideWidth;
        }

        set
        {
            if (value != _collideWidth)
            {
                _collideWidth = value;
                NotifyPropertyChanged();
            }
        }
    }

    public double CollideHeight
    {
        get
        {
            return _collideHeight;
        }

        set
        {
            if (value != _collideHeight)
            {
                _collideHeight = value;
                NotifyPropertyChanged();
            }
        }
    }
}

As you can see- mostly public properties, plus implementation of INotifyPropertyChanged interface.

Attached property definition:

public static readonly DependencyProperty CollisionX = DependencyProperty.RegisterAttached(
      "CollisionX",
      typeof(double),
      typeof(ResourceBorderControl),
      new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender)
    );

    public static void SetCollisionX(UIElement element, object value)
    {
        element.SetValue(CollisionX, (double)value);
    }

    public static double GetCollisionX(UIElement element)
    {
        return (double)element.GetValue(CollisionX);
    }
هل كانت مفيدة؟

المحلول 2

I figured out how to make a workaround, but this is ugly hack, so if anyone is able to point me in the right direction I would appreciate that :). I added DataTriggers, so now my DataTemplate.Triggers XAML node looks like this:

<DataTemplate.Triggers>
                            <DataTrigger Binding="{Binding IsSelected}" Value="true">
                                <Setter Property="BorderBrush" Value="Purple" TargetName="ResRect"/>
                            </DataTrigger>

                            <DataTrigger Binding="{Binding IsSelected}" Value="false">
                                <Setter Property="BorderBrush" Value="SeaGreen" TargetName="ResRect"/>
                            </DataTrigger>

                            <!-- very very ugly ugly hack :(-->
                            <DataTrigger Binding="{Binding IsSelected}" Value="true">
                                <Setter Property="Canvas.Left" Value="{Binding CollideX}" TargetName="CollideRect"/>
                                <Setter Property="Canvas.Top" Value="{Binding CollideY}" TargetName="CollideRect"/>
                                <Setter Property="FrameworkElement.Width" Value="{Binding CollideWidth}" TargetName="CollideRect"/>
                                <Setter Property="FrameworkElement.Height" Value="{Binding CollideHeight}" TargetName="CollideRect"/>
                            </DataTrigger>

                            <DataTrigger Binding="{Binding IsSelected}" Value="false">
                                <Setter Property="Canvas.Left" Value="{Binding CollideX}" TargetName="CollideRect"/>
                                <Setter Property="Canvas.Top" Value="{Binding CollideY}" TargetName="CollideRect"/>
                                <Setter Property="FrameworkElement.Width" Value="{Binding CollideWidth}" TargetName="CollideRect"/>
                                <Setter Property="FrameworkElement.Height" Value="{Binding CollideHeight}" TargetName="CollideRect"/>
                            </DataTrigger>
                        </DataTemplate.Triggers>

I am not happy about it, but at least it works.

نصائح أخرى

Your attached property looks OK but I think you're misunderstanding the relationship of the ItemTemplate and ItemContainerStyle. It looks like you're trying to set the properties that you're binding in the ItemContainerStyle and have them surface on your ResourceBorderControl inside the DataTemplate which will be injected into the item container. What you're actually getting with what you have now is a visual tree like this:

ItemsControl
 ItemsPresenter
  Canvas
   ContentPresenter for item 1 (ResourceBorderControl.CollisionX = bound value, etc)
    ResourceBorderControl
     Canvas
      ...
   ContentPresenter for item 2 (ResourceBorderControl.CollisionX = bound value, etc)
    ResourceBorderControl
     Canvas
      ...

There are two ways you can handle this depending on what your ultimate goal is. If you just want to get the properties to show up down on the ResourceBorderControl, you can take advantage of property inheritance by changing your DP definition to include an extra Inherits flag:

public static readonly DependencyProperty CollisionX = DependencyProperty.RegisterAttached(
      "CollisionX",
      typeof(double),
      typeof(ResourceBorderControl),
      new FrameworkPropertyMetadata(0.0,
          FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsRender)
    );

This will cause the property to be set on each child below the ContentPresenter ItemContainer unless overridden or cleared.

Your other choice would be to derive a custom ItemsControl which uses ResourceBorderControl as its container. This involves just overriding a few methods, I think GetContainerForItemOverride and IsItemItsOwnContainerOverride are the minimum. This gives you the benefit of your custom control being a direct child of the ItemsPanel's Canvas, which can open up lots of different layout options.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top