如何通过代码设置WPF画布子元素的x、y坐标?

12

如何通过代码设置WPF画布子元素的x、y坐标?以下是我的示例代码。

Canvas root = new Canvas();
double y = 5;
for (int i=0; i< 10; i++)
{
    Ellipse e = new Ellipse();
    e.Height=10;
    e.Width=10;
    e.Stroke =Brushes.Black;

    root.Children.Add(e);
    y +=10;
}

MyRootCanvas = root;

MyRootCanvas是一个Canvas类型的属性,绑定到WPF UserControl的内容。


@HighCore 我需要动态加载控件。 - user2951819
1
然后使用ItemsControl。WPF对“动态”的理解与其他技术非常不同。我强烈建议您学会正确使用WPF,否则由于可视化树的复杂性,您将遇到各种麻烦。 - Federico Berasategui
@HighCore 我正在使用一个 ItemsControl。我的 UserControl 被加载在一个 ItemsControl 中。 - user2951819
如果您正在使用 ItemsControl,那么就没有必要在过程代码中创建或操作 UI 元素。这就是 XAML 的作用。请正确使用数据绑定。 - Federico Berasategui
@HighCore,有100多个元素需要填充。我们决定动态地完成它。但由于我们对WPF还很陌生,所以任何帮助都将不胜感激。 - user2951819
1
如果您有很多UI元素,正确的DataBinding方法会更好,因为它可以通过使用UI虚拟化来提高性能,而您当前的过程式代码方法则不会。从MSDN以下是禁用UI虚拟化的条件列表: - 项目容器直接添加到ItemsControl中。例如,如果应用程序显式地将ListBoxItem对象添加到ListBox中,则ListBox不会虚拟化ListBoxItem对象。 - Federico Berasategui
2个回答

22

使用Canvas.SetLeftCanvas.SetTop方法为子元素设置x、y坐标:

for (int I=1; I<= 10; I++)
{
    Ellipse e = new Ellipse();
    e.Height=10;
    e.Width=10;
    e.Stroke =Brushes.Black;

    Canvas.SetLeft(e, 10); <-- HERE
    Canvas.SetTop(e, Y);

    root.Children.Add(e);
    Y +=10;
}

你确定顺序没问题吗?孩子的位置不应该在添加之前设置吗?无论如何,我已经这样做了,它可以工作,因此我会标记您的答案,但请为其他用户纠正它。 - user2951819
4
Canvas.SetLeftCanvas.SetTop 是 Canvas 的静态方法。在将元素添加到 Canvas 之前,您可以设置它们。在父元素中添加项目之前,始终可以设置附加属性。 - Rohit Vats

16

我知道这个问题已经得到了回答,但是HighCore所说的(即不要在代码中操纵UI元素)足以强调。为了正确地做到这一点,您应该创建一个类来封装您想要显示的数据:

public class Widget : ViewModelBase
{
    private double _X;
    public double X
    {
        get { return _X;}
        set { _X = value; RaisePropertyChanged(() => this.X); }
    }

    private double _Y;
    public double Y
    {
        get { return _Y;}
        set { _Y = value; RaisePropertyChanged(() => this.Y); }
    }

    private double _Width;
    public double Width
    {
        get { return _Width;}
        set { _Width = value; RaisePropertyChanged(() => this.Width); }
    }

    private double _Height;
    public double Height
    {
        get { return _Height;}
        set { _Height = value; RaisePropertyChanged(() => this.Height); }
    }

    private System.Windows.Media.Color _Color;
    public System.Windows.Media.Color Color
    {
        get { return _Color;}
        set { _Color = value; RaisePropertyChanged(() => this.Color); }
    }
}

每次为每个参数创建这些依赖属性有点烦人,我使用内置的代码片段管理器为自己创建一个片段,这样我就不必每次都键入整个内容。接下来,您想要创建一个包含这些数组并将它们放在某个视图模型中,那就是生成省略号逻辑应该去的地方:

public MyViewModel()
{
    for (int y = 0; y < 10; y++)
        for (int x = 0; x < 10; x++)
            this.Items.Add(new Widget {
                X = x * 20,
                Y = y * 20,
                Width = 10,
                Height = 10,
                Color = Color.FromArgb(255, (byte)(x * 20), (byte)(y * 20), 0)
            });
}

然后,您将设置MyViewModel的实例作为包含画布的窗口的数据上下文。剩下的就是XAML,它会松散地绑定到此视图模型,您想要在画布上呈现项目列表,因此使用ItemsControl并将默认项面板替换为画布:

<ItemsControl ItemsSource="{Binding Items}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Ellipse Width="{Binding Width}" Height="{Binding Height}">
                <Ellipse.Stroke>
                    <SolidColorBrush Color="{Binding Color}" />
                </Ellipse.Stroke>
            </Ellipse>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemContainerStyle>
        <Style>
            <Setter Property="Canvas.Left" Value="{Binding X}" />
            <Setter Property="Canvas.Top" Value="{Binding Y}" />
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>

这里是结果:

circles

现在你的前端数据与视图模型绑定,所以你可以添加/删除项目或更改单个属性等,更改将自动传播。你还可以进行单元测试来检查逻辑,而无需创建任何视图控件。


1
我知道这篇帖子是几年前的,但我刚刚发现它很有用。 - Brock
1
@Brock 谢谢!很高兴看到旧帖子仍然能帮助人们! - Mark Feldman
如果我稍后(例如响应按钮)添加或删除小部件到/从项目中,则UI不会更新。我尝试了额外的RaisePropertyChange调用,但没有成功。更改属性(例如X或Y)会更新UI,但删除或添加则不会。有什么建议吗?我已经几十年没有在Windows上编程了。 - Brock
1
@Brock,看起来你的“Items”集合不支持INotifyCollectionChanged,请将其更改为ObservableCollection<T>类型。 - Mark Feldman
1
你真棒。当我修改集合时,我还必须使用 App.Current.Dispatcher.Invoke((Action)delegate,但现在它可以工作了。 - Brock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接