我有ItemsControl,其中ItemsPanel是Canvas。数据模板是一个包裹在Border中的Canvas,它中还有一个Rectangle。这是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>
如您所见,我正在使用自定义边框控件 - ResourceBorderControl。它有4个附加属性,用于描述内部矩形: CollisionX CollisionY CollisionWidth CollisionHeight
如果我只是在XAML中对附加属性(CollisionX等)进行硬编码,它的工作正常 - 画布中的矩形按照我想要的方式呈现。
我希望使用样式(或任何其他方式,如果可能)来设置它,因为我需要使用来自ItemsSource的渲染对象的值来填充它。
它只是不起作用。我甚至无法在WPF树可视化工具中调试它 - 当我尝试查看ResourceBorderControl对象时,我看不到碰撞值。当我运行程序时,我可以看到Border with Canvas的渲染(我可以看到不透明度),但它没有任何内容。我想这些值在由Style Setters设置后会丢失。
所以我的问题是 - 我在这里做错了吗?或者只是WPF不允许使用样式设置自定义附加值,我应该使用另一种方法?如果是后者,我还能怎么做呢?
编辑: 这是实例在ObservableCollection中绑定到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();
}
}
}
}
正如您所看到的 - 主要是公共属性,以及INotifyPropertyChanged接口的实现。
附属物定义:
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);
}
答案 0 :(得分:2)
您附加的属性看起来不错,但我认为您误解了ItemTemplate和ItemContainerStyle之间的关系。看起来您正在尝试设置您在ItemContainerStyle中绑定的属性,并将它们放在DataTemplate内的ResourceBorderControl上,并将其注入到项容器中。你现在拥有的是一个像这样的可视树:
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
...
根据您的最终目标,有两种方法可以解决这个问题。如果您只想让属性显示在ResourceBorderControl上,您可以通过更改DP定义以包含额外的Inherits标志来利用属性继承:
public static readonly DependencyProperty CollisionX = DependencyProperty.RegisterAttached(
"CollisionX",
typeof(double),
typeof(ResourceBorderControl),
new FrameworkPropertyMetadata(0.0,
FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsRender)
);
这将导致在ContentPresenter ItemContainer下面的每个子项上设置属性,除非被覆盖或清除。
您的另一个选择是派生自定义ItemsControl,它使用ResourceBorderControl作为其容器。这只涉及覆盖一些方法,我认为GetContainerForItemOverride
和IsItemItsOwnContainerOverride
是最小的。这使您的自定义控件成为ItemsPanel的Canvas的直接子项,可以打开许多不同的布局选项。
答案 1 :(得分:0)
我想出了如何制定解决方法,但这是丑陋的黑客,所以如果有人能够指出我正确的方向,我会很感激:)。我添加了DataTriggers,所以现在我的DataTemplate.Triggers XAML节点如下所示:
<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>
我对此并不满意,但至少它有效。