1

我想通过用户输入添加画布元素。类似于单击按钮时<Ellipse/>,会在 Canvas 内的 XAML 文件中添加一个新元素。

<Canvas x:Name="GraphDisplayFrame" Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="3" Grid.RowSpan="4">
            <Ellipse
                Width="50"
                Height="50"
                Stroke="Black"
                StrokeThickness="2"
                Canvas.Left="100"
                Canvas.Top="100" />
</Canvas>

我是 WPF 的新手,我不确定这是否是正确的方法。

我正在尝试的另一件事是 System.Windows.Media 但操作 XAMl 文件看起来更容易和更好,因为那时绘图的位置被锚定在画布上。我不确定是否可以使用 System.Windows.Media 实现类似的功能。

所以我的问题在标题中,但我对其他建议持开放态度。

4

1 回答 1

2

您可能想了解 WPF 中的绑定。假设您希望Ellipse通过用户输入(例如Button点击)将您的 s 添加到您的Canvas. 我不确定 Canvas 是否用于此目的(它没有自动对齐子元素),所以我WrapPanel改用了(允许它对齐项目)。我们需要 2Button秒(添加删除 Ellipse)。我添加了一个Label来显示我们拥有的当前椭圆的数量。

XAML

<Window x:Class="WpfApp2.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:WpfApp2"
        mc:Ignorable="d"
        Name ="mainWindow"
        Title="Main Window"
        Width="800"
        MaxWidth="800"
        Height="450"
        MaxHeight="450">
    <Grid x:Name="MainGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50*"/>
            <ColumnDefinition Width="50*"/>
            <ColumnDefinition Width="50*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="50*"/>
            <RowDefinition Height="50*"/>
            <RowDefinition Height="50*"/>
            <RowDefinition Height="50*"/>
        </Grid.RowDefinitions>
        <Label Content="{Binding ElementName=mainWindow, Path=EllipsesCount, UpdateSourceTrigger=PropertyChanged}" 
               HorizontalContentAlignment="Center"
               VerticalContentAlignment="Center" 
               Grid.Row="0" 
               Background="DimGray" 
               Foreground="White"
               Margin="15,35" />
        <Button x:Name="BtnAddEllipse" 
                        Content="ADD ELLIPSE" 
                        Grid.Row="1" 
                        Margin="10, 25" FontSize="22" FontWeight="Bold"
                        Background="LightGreen"/>
        <Button x:Name="BtnRemoveEllipse" 
                        Content="REMOVE ELLIPSE" 
                        Grid.Row="2" 
                        Margin="10, 25" FontSize="22" FontWeight="Bold"
                        Background="IndianRed"/>
        <WrapPanel Orientation="Horizontal" 
                   Background="Gainsboro"
                   HorizontalAlignment="Stretch"
                   VerticalAlignment="Stretch"
                   Grid.Column="1" 
                   Grid.Row="0" 
                   Grid.ColumnSpan="3"
                   Grid.RowSpan="4" >
            <ItemsControl ItemsSource="{Binding ElementName=mainWindow, Path=Ellipses, UpdateSourceTrigger=PropertyChanged}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </WrapPanel>
    </Grid>
</Window>

在这里,您可以看到该Label.Content属性绑定到某个 EllipsesCount 属性(您将在下面的代码隐藏中看到它)。也WrapPanel绑定到 Ellipses 属性。

代码隐藏:(用于复制粘贴目的)

using System;
using System.Linq;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

namespace WpfApp2
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        // Text for Label about Ellipses amount in collection
        private object _ellipsesCount = "Current ellipses count: 0";
        public object EllipsesCount
        {
            get => _ellipsesCount;
            set
            {
                _ellipsesCount = "Current ellipses count: " + value;
                // When we set new value to this property - 
                // we call OnPropertyChanged notifier, so Label
                // would be "informed" about this change and will get new value
                OnPropertyChanged(nameof(EllipsesCount));
            }
        }

        // Collection for Ellipses
        private ObservableCollection<Ellipse> _ellipses;
        public ObservableCollection<Ellipse> Ellipses
        {
            get => _ellipses;
            set
            {
                _ellipses = value;                   
                OnPropertyChanged(nameof(Ellipses));
            }
        }

        // Hanlder, which would notify our Controls about property changes, so they will "update" itself with new values
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName] string propertyName = "") =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        // Just for random colors
        private readonly Random random = new Random();

        public MainWindow()
        {
            InitializeComponent();

            // Initialize collection of Ellipses
            Ellipses = new ObservableCollection<Ellipse>();
            // Handle when collection is changed to update Label
            // with a new amount of Ellipses
            Ellipses.CollectionChanged += delegate 
            {
                // Update counter of ellipses when new one added or existing removed
                EllipsesCount = Ellipses.Count;
            };

            BtnAddEllipse.Click += delegate
            {
                // Create an Ellipse with random stroke color
                var ellipse = new Ellipse
                {
                    Width = 50,
                    Height = 50,
                    Margin = new Thickness(3),
                    Stroke = new SolidColorBrush(Color.FromRgb((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255))),
                    StrokeThickness = 3
                };
                // Add to collection of ellipses
                Ellipses.Add(ellipse);
            };
            BtnRemoveEllipse.Click += delegate
            {
                // Check, that Ellipses collection isn't null and empty,
                // so we can remove something from it
                if (Ellipses?.Count > 0)
                    Ellipses.Remove(Ellipses.Last()); // Removing last element
            };
        }
    }
}

所以结果你实际上看到的是“椭圆集合的内容”,而不是直接将椭圆添加到窗口中。绑定使得WrapPanel使用 Ellipse 的集合作为子元素的来源,这应该是其中的WrapPanel(而不是我原来的答案,我们将 Ellipse 作为子元素添加到 Canvas)。

在此处输入图像描述


原始答案。

是的,你可以。例如(基于您的 XAML):

XAML(空窗口):

<Window x:Class="WPFApp.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:WPFApp"
        mc:Ignorable="d">
    <!-- No even Grid here -->
</Window>

代码隐藏(也检查评论):


    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            // Setting Window properties (they not exists in XAML)
            // XAML: <Window ... Title="Main Window" Height="450" Width="800">...
            this.Title = "Main Window";
            this.Height = 450;
            this.Width = 800;

// Create main Grid and register some its name // XAML: ... var mainGrid = new System.Windows.Controls.Grid(); this.RegisterName("MainGrid", mainGrid);
// Add row and column definitions (as Canvas below needs, at least 4 rows and 3 columns) for (int i = 0; i < 4; i++) { mainGrid.RowDefinitions.Add(new System.Windows.Controls.RowDefinition { Height = new GridLength(50, GridUnitType.Star) });
if (i < 3) // Needn't 4th column mainGrid.ColumnDefinitions.Add(new System.Windows.Controls.ColumnDefinition { Width = new GridLength(50, GridUnitType.Star) }); }
// Create Canvas and register its name too // XAML: ... var canvas = new System.Windows.Controls.Canvas { // Just to be able see it at Window Background = System.Windows.Media.Brushes.LightGray }; this.RegisterName("GraphDisplayFrame", canvas); canvas.SetValue(System.Windows.Controls.Grid.ColumnProperty, 1); canvas.SetValue(System.Windows.Controls.Grid.RowProperty, 0); canvas.SetValue(System.Windows.Controls.Grid.ColumnSpanProperty, 3); canvas.SetValue(System.Windows.Controls.Grid.RowSpanProperty, 4);
// Create Ellipse (child canvas element) // XAML: ... var ellipse = new System.Windows.Shapes.Ellipse { Width = 50, Height = 50, Stroke = System.Windows.Media.Brushes.Black, StrokeThickness = 2 }; ellipse.SetValue(System.Windows.Controls.Canvas.LeftProperty, 100D); ellipse.SetValue(System.Windows.Controls.Canvas.TopProperty, 100D);
// Add child Ellipse to Canvas canvas.Children.Add(ellipse); // or you already can find Canvas by its name: (this.FindName("GraphDisplayFrame") as System.Windows.Controls.Canvas).Children.Add(ellipse);
// Add Canvas to MainGrid. Find Grid by its registered name too
(this.FindName("MainGrid") as System.Windows.Controls.Grid).Children.Add(canvas);
// Set main Grid as window content this.Content = mainGrid; } }

因此,如您所见,XAML 标记非常紧凑,即代码隐藏标记。

在此处输入图像描述

于 2021-11-18T15:37:55.967 回答