Gergő S
Gergő S

Reputation: 77

Can i add elements programatically to XAML? WPF c#

I want to add canvas elements by user input. Something like when a button is clicked, a new <Ellipse/> element is added to the XAML file, inside the Canvas.

<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>

I'm new to WPF, i'm not sure if this is the right way to do this.

The other thing i'm trying is System.Windows.Media but manipulating the XAMl file looks easier and nicer, since then the locations of the drawings are anchored to the canvas. I'm not sure if i can achieve something similar with System.Windows.Media.

So my question is in the title, but I'm open to other suggestions.

Upvotes: 2

Views: 1661

Answers (1)

Auditive
Auditive

Reputation: 2049

You probably want to learn about Bindings in WPF. Let's say you want your Ellipses be added by user's input (e.g. on Button click) to your Canvas. I'm not sure about Canvas usage for that purpose (it hasn't auto-alignments for child elements), so I used WrapPanel instead (to allow it align items). And we need 2 Buttons (to Add and Remove Ellipses). And I add a Label to display current amount of Ellipses that we have.

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>

Here you see that Label.Content property is binded to some EllipsesCount property (you'll see it in code-behind below). Also as WrapPanel is binded to Ellipses property.

Code-behind: (for copypaste purpose)

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
            };
        }
    }
}

So at result you see, actually, "content of collection of Ellipses", without adding Ellipses directly to window. Binding makes WrapPanel to use collection of Ellipses as source of child elements, that should be in that WrapPanel (instead of original my answer, where we add Ellipse to Canvas as Children).

enter image description here


ORIGINAL answer.

Yes, you can. For example (based on your XAML):

XAML (empty window):

<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>

Code-behind (check comments also):


    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; } }

So, as you can see, XAML markuping is quite more compact, that code-behinded one.

enter image description here

Upvotes: 2

Related Questions