我是使用WPF的新手,但非常喜欢在xaml中编写视图代码并在视图模型中支持代码的想法。我想要做的是扩展Canvas的使用,方法是将它与状态栏相关联,状态栏根据画布的内容和鼠标位置显示状态文本(下面的程式化代码不包括此内容)。
我的方法是创建一个包含Canvas的UserControl,并根据https://www.codeproject.com/Articles/82464/How-to-Embed-Arbitrary-Content-in-a-WPF-Control将ContentPresenter放入其中。
我有两个问题需要解决: 1)我需要做什么才能允许多个子控件与Canvas允许多个子控件相同的方式? 2)如何从主窗口代码访问Canvas的属性,例如Canvas.Left?
提前感谢您提出的任何建议。
UserControl xaml代码,后面的UserControl代码和主窗口xaml代码:
<UserControl x:Class="SO.CanvasUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SO"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Template>
<ControlTemplate TargetType="{x:Type local:CanvasUserControl}">
<Canvas Width="200" Height="100" Background="Green">
<ContentPresenter/>
</Canvas>
</ControlTemplate>
</UserControl.Template>
</UserControl>
代码背后:
public partial class CanvasUserControl : UserControl
{
public CanvasUserControl()
{
InitializeComponent();
}
}
主窗口:
<Window x:Class="SO.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SO"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<!-- works as expected
<Canvas Width="200" Height="100" Background="Green">
<Line X1="0" Y1="0" X2="200" Y2="100" Stroke="Red"/>
<Line X1="200" Y1="0" X2="0" Y2="100" Stroke="Red"/>
</Canvas>
-->
<!-- works as expected
<Canvas Width="200" Height="100" Background="Green" x:Name="MyCanvas">
<Line X1="{Binding ElementName=MyCanvas, Path=Left}" Y1="{Binding ElementName=MyCanvas, Path=Top}" X2="{Binding ElementName=MyCanvas, Path=ActualWidth}" Y2="{Binding ElementName=MyCanvas, Path=ActualHeight}" Stroke="Red"/>
<Line X1="{Binding ElementName=MyCanvas, Path=ActualWidth}" Y1="{Binding ElementName=MyCanvas, Path=Top}" X2="{Binding ElementName=MyCanvas, Path=Left}" Y2="{Binding ElementName=MyCanvas, Path=ActualHeight}" Stroke="Red"/>
</Canvas>
-->
<!-- How do I add more than one child control as nested content for the Canvas?
<local:CanvasUserControl x:Name="MyCanvasUserControl">
<Line X1="0" Y1="0" X2="200" Y2="100" Stroke="Red"/>
<Line X1="200" Y1="0" X2="0" Y2="100" Stroke="Green"/>
</local:CanvasUserControl>
-->
<!-- How do I access dependency properties of the Canvas?
<local:CanvasUserControl x:Name="MyCanvasUserControl">
<Line X1="{Binding ElementName=MyCanvasUserControl, Path=Left}" Y1="{Binding ElementName=MyCanvasUserControl, Path=Top}" X2="{Binding ElementName=MyCanvasUserControl, Path=ActualWidth}" Y2="{Binding ElementName=MyCanvasUserControl, Path=ActualHeight}" Stroke="Red"/>
</local:CanvasUserControl>
-->
</Grid>
</Window>
答案 0 :(得分:1)
我把它放在一起非常快,但它可以在WPF中运行并演示控制子类,加上ItemsControl
自定义。
请注意,没有viewmodel。我们不需要一个,在自定义控件中我们不需要一个。 Viewmodels由消费者提供;当你编写自定义控件时,你正在冒充框架。自定义控件设计用于其他人的视图模型,因此必须单独留出DataContext
。如果你不这样做,你会发现自己一直在发明变通办法。目标是编写一些与标准WPF控件完全相同的东西。
StatusBarCanvas.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
namespace CanvasUserControl
{
public class StatusBarCanvas : ItemsControl
{
public StatusBarCanvas()
{
}
static StatusBarCanvas()
{
// This lets the framework finds the implicit style in
// Themes\Generic.xaml (below)
DefaultStyleKeyProperty.OverrideMetadata(
typeof(StatusBarCanvas),
new FrameworkPropertyMetadata(typeof(StatusBarCanvas)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var itemsPresenter = (ItemsPresenter)GetTemplateChild("PART_ItemsPresenter");
// The ItemsPresenter isn't ready yet right now, so do this
// when it's Loaded.
itemsPresenter.Loaded += (s,e) =>
{
Canvas = GetVisualChild<CustomCanvas>(itemsPresenter);
};
}
#region Helpers
// Stolen from here: http://stackoverflow.com/a/7321019/424129
private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
//System.Diagnostics.Trace.WriteLine($"Visual Child {child}");
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
#endregion Helpers
#region Canvas Property
/// <summary>
/// Gets the canvas control.
/// This is a readonly dependency property. Don't let anybody try to replace
/// the Canvas on us.
/// </summary>
public CustomCanvas Canvas
{
get { return (CustomCanvas)GetValue(CanvasProperty); }
protected set { SetValue(CanvasPropertyKey, value); }
}
internal static readonly DependencyPropertyKey CanvasPropertyKey =
DependencyProperty.RegisterReadOnly("Canvas", typeof(CustomCanvas),
typeof(StatusBarCanvas), new PropertyMetadata(null));
public static readonly DependencyProperty CanvasProperty =
CanvasPropertyKey.DependencyProperty;
#endregion Canvas Property
}
public class CustomCanvas : Canvas
{
public CustomCanvas()
{
// This event is only happening for me on enter/leave.
// Fortunately that's not the question you're asking.
MouseMove += CustomCanvas_MouseMove;
}
protected void UpdateMousePosition()
{
MousePosition = Mouse.GetPosition(this);
System.Diagnostics.Trace.
WriteLine($"MouseMove {DateTime.Now} Position {MousePosition}");
}
private void CustomCanvas_MouseMove(object sender, MouseEventArgs e)
{
UpdateMousePosition();
}
#region MousePosition Property
/// <summary>
/// Gets the mouse position relative to the Canvas
/// Again, readonly.
/// </summary>
public Point MousePosition
{
get { return (Point)GetValue(MousePositionProperty); }
protected set { SetValue(MousePositionPropertyKey, value); }
}
internal static readonly DependencyPropertyKey MousePositionPropertyKey =
DependencyProperty.RegisterReadOnly("MousePosition",
typeof(Point), typeof(CustomCanvas), new PropertyMetadata(null));
public static readonly DependencyProperty MousePositionProperty =
MousePositionPropertyKey.DependencyProperty;
#endregion MousePosition Property
}
}
主题\ Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CanvasUserControl"
>
<Style TargetType="local:StatusBarCanvas">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:StatusBarCanvas">
<Grid
Margin="{TemplateBinding Padding}"
>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- The Border has no Row, so it fills the whole grid
behind the other controls -->
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
/>
<ItemsPresenter
x:Name="PART_ItemsPresenter"
Grid.Row="0"
/>
<StatusBar
Grid.Row="1"
x:Name="PART_StatusBar"
>
<StatusBarItem
Content="{Binding Canvas.MousePosition, RelativeSource={RelativeSource TemplatedParent}}"
ContentStringFormat="{}({0})"
/>
</StatusBar>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<local:CustomCanvas
/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
MainWindow.xaml
<Grid>
<local:StatusBarCanvas
x:Name="TestCanvas"
Width="200"
BorderBrush="Black"
BorderThickness="1"
>
<Line
X1="0" Y1="0"
X2="{Binding Canvas.ActualHeight, ElementName=TestCanvas}"
Y2="{Binding Canvas.ActualWidth, ElementName=TestCanvas}"
Stroke="DeepSkyBlue"
StrokeThickness="2"
/>
<Line
X1="20" Y1="20"
X2="100"
Y2="20"
Stroke="OrangeRed"
StrokeThickness="4"
/>
<Label
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Content="Foo Bar Planxty"
Canvas.Left="100"
Canvas.Top="100"
/>
</local:StatusBarCanvas>
</Grid>
答案 1 :(得分:0)
也许我并没有完全理解你的问题,但听起来你只想拥有一个内置预定义状态栏的Canvas。您可以通过扩展Canvas而不是UserControl来轻松完成此操作。这是一个扩展Canvas并具有状态栏的自定义组件。
<Canvas x:Class="SO.CanvasUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SO"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Border Width="525" Height="30" Background="Black" Canvas.Bottom="0">
<TextBlock Foreground="White" Text="Hello world" FontSize="16" />
</Border>
</Canvas>
现在将其添加到您的主窗口并分配您喜欢的任何孩子。您将看到状态栏与所有子项一起显示。由于组件扩展了Canvas,您可以根据需要添加任意数量的子项,并且可以绑定到Canvas依赖项属性。
<Window x:Class="SO.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SO"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<local:CanvasUserControl x:Name="MyCanvasUserControl" >
<Line X1="0" Y1="0" X2="200" Y2="100" Stroke="Red"/>
<Line X1="200" Y1="0" X2="0" Y2="100" Stroke="Green"/>
<Line X1="{Binding ElementName=MyCanvasUserControl, Path=Left}" Y1="{Binding ElementName=MyCanvasUserControl, Path=Top}" X2="{Binding ElementName=MyCanvasUserControl, Path=ActualWidth}" Y2="{Binding ElementName=MyCanvasUserControl, Path=ActualHeight}" Stroke="Red"/>
</local:CanvasUserControl>
</Window>