bc291
bc291

Reputation: 1171

Add UserControl dynamically in WPF C#

I've got one page in my WPF app that should display some "tiles" in number as I specify before. Tile looks like this:

enter image description here

So my page should look something like this: enter image description here

It is achievable of course by manually cloning tiles, but I want to avoid this (achieve it in more programmatic way). So instead of creating 6 clones I should stick to only one and then if needed add remaining ones. How can I accomplish that? I guess I should create my own UserControl like this:

 <Grid HorizontalAlignment="Left" Height="199" VerticalAlignment="Top" Width="207" Background="Black">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="0*"/>
        </Grid.RowDefinitions>
        <Image x:Name="image1" HorizontalAlignment="Left" Height="175" VerticalAlignment="Top" Width="207" Stretch="UniformToFill"/>
        <Grid HorizontalAlignment="Left" Height="30" VerticalAlignment="Top" Width="112" Background="#FFC78D10">
            <TextBox  IsReadOnly = "True"  x:Name="CategoryOfEvent" Height="30" TextWrapping="Wrap" Text="Category" Width="112" Background="{x:Null}" BorderBrush="{x:Null}"  Foreground="White" FontSize="18" SelectionBrush="{x:Null}" HorizontalAlignment="Left" VerticalAlignment="Top" >
                <TextBox.Template>
                    <ControlTemplate TargetType="{x:Type TextBox}">
                        <ScrollViewer Name="PART_ContentHost"/>
                    </ControlTemplate>
                </TextBox.Template>

            </TextBox>
        </Grid>
        <TextBox  IsReadOnly = "True"  x:Name="HourOfEvent"  HorizontalAlignment="Left" Height="28" Margin="0,42,0,0" TextWrapping="Wrap" Text="Hour" VerticalAlignment="Top" Width="148" Background="{x:Null}" BorderBrush="{x:Null}"  Foreground="#FFE2E2E2" FontSize="22" SelectionBrush="{x:Null}" FontWeight="Bold" TextChanged="HourOfEvent_TextChanged">
            <TextBox.Template>
                <ControlTemplate TargetType="{x:Type TextBox}">
                    <ScrollViewer Name="PART_ContentHost"/>
                </ControlTemplate>
            </TextBox.Template>
        </TextBox>
        <TextBox  IsReadOnly = "True" x:Name="TitleOfEvent" HorizontalAlignment="Left" Height="88" Margin="0,82,0,0" TextWrapping="Wrap" Text="Title" VerticalAlignment="Top" Width="207" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" FontSize="20" SelectionBrush="{x:Null}" FontWeight="Bold">
            <TextBox.Template>
                <ControlTemplate TargetType="{x:Type TextBox}">
                    <ScrollViewer Name="PART_ContentHost"/>
                </ControlTemplate>
            </TextBox.Template>
        </TextBox>
        <TextBox  IsReadOnly = "True"  x:Name="PlaceOfEvent" HorizontalAlignment="Left" Height="24" Margin="0,175,0,0" TextWrapping="Wrap" Text="Where" VerticalAlignment="Top" Width="207" Background="{x:Null}" BorderBrush="{x:Null}"  Foreground="White" FontSize="14" SelectionBrush="{x:Null}">
            <TextBox.Template>
                <ControlTemplate TargetType="{x:Type TextBox}">
                    <ScrollViewer Name="PART_ContentHost"/>
                </ControlTemplate>
            </TextBox.Template>
        </TextBox>

    </Grid>

and just add them to my page. I would like also to mention that in every tiles there are 4 textboxes which are displaying some data parsed from Json, so maybe some automatic binding should do the job?

Upvotes: 0

Views: 1533

Answers (2)

ZiggZagg
ZiggZagg

Reputation: 1427

The follwing example shows how to create multiple of the tiles you have been posting using a DataTemplate and WrapPanel. The DataTemplate specifies how an object (in this case a TileItem) is visualized. You can create multiple TileItems and then add them to an collection, in order to visualize them all.

Assuming your UI resides in MainWindow, you can create a collection with three items in it.

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        TileItemCollection = new ObservableCollection<TileItem>(new []
        {
            new TileItem(){Category = "Alpha", Hour = "10", Title = "Hello World", Where = "Office"}, 
            new TileItem(){Category = "Beta", Hour = "15", Title = "Test", Where = "Home"}, 
            new TileItem(){Category = "Gamma", Hour = "44", Title = "My Title", Where = "Work"}, 
        });

        DataContext = this;
    }

    public ObservableCollection<TileItem> TileItemCollection { get; }
}

You could load your Items from JSON and create an TileItem for each one in the JSON document. The class for TileItemss can be found below.

public class TileItem : INotifyPropertyChanged
{
    private string _hour;
    private string _title;
    private string _where;
    private string _category;

    public string Category
    {
        get => _category;
        set
        {
            if (value == _category) return;
            _category = value;
            OnPropertyChanged();
        }
    }

    public string Hour
    {
        get => _hour;
        set
        {
            if (value == _hour) return;
            _hour = value;
            OnPropertyChanged();
        }
    }

    public string Title
    {
        get => _title;
        set
        {
            if (value == _title) return;
            _title = value;
            OnPropertyChanged();
        }
    }

    public string Where
    {
        get => _where;
        set
        {
            if (value == _where) return;
            _where = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Note that in order for datachanges to be propagated to the UI, all properties which should be updated in the UI when you update them in code need to raise the property changed event. In this example all properties do this by default.

You can then update the XAML to bind to a collection. The ItemsControl acts as a container for the tiles. If you scroll down further you may notice the use of WrapPanel which is responsible for the item wrapping effect when you resize the control.

<ItemsControl ItemsSource="{Binding TileItemCollection}" Margin="20">
    <ItemsControl.ItemTemplate>
        <DataTemplate  DataType="{x:Type local:TileItem}" >
            <Grid HorizontalAlignment="Left" Height="199" VerticalAlignment="Top" Width="207" Background="Black">
                <Grid.RowDefinitions>
                    <RowDefinition/>
                    <RowDefinition Height="0*"/>
                </Grid.RowDefinitions>
                <Image x:Name="image1" HorizontalAlignment="Left" Height="175" VerticalAlignment="Top" Width="207" Stretch="UniformToFill"/>
                <Grid HorizontalAlignment="Left" Height="30" VerticalAlignment="Top" Width="112" Background="#FFC78D10">
                    <TextBox IsReadOnly="True" Height="30" TextWrapping="Wrap" Text="{Binding Path=Category}" Width="112" Background="{x:Null}" BorderBrush="{x:Null}"  Foreground="White" FontSize="18" SelectionBrush="{x:Null}" HorizontalAlignment="Left" VerticalAlignment="Top" >
                        <TextBox.Template>
                            <ControlTemplate TargetType="{x:Type TextBox}">
                                <ScrollViewer Name="PART_ContentHost"/>
                            </ControlTemplate>
                        </TextBox.Template>
                    </TextBox>
                </Grid>
                <TextBox IsReadOnly="True" HorizontalAlignment="Left" Height="28" Margin="0,42,0,0" TextWrapping="Wrap" Text="{Binding Path=Hour}" VerticalAlignment="Top" Width="148" Background="{x:Null}" BorderBrush="{x:Null}"  Foreground="#FFE2E2E2" FontSize="22" SelectionBrush="{x:Null}" FontWeight="Bold">
                    <TextBox.Template>
                        <ControlTemplate TargetType="{x:Type TextBox}">
                            <ScrollViewer Name="PART_ContentHost"/>
                        </ControlTemplate>
                    </TextBox.Template>
                </TextBox>
                <TextBox IsReadOnly="True" HorizontalAlignment="Left" Height="88" Margin="0,82,0,0" TextWrapping="Wrap" Text="{Binding Path=Title}" VerticalAlignment="Top" Width="207" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" FontSize="20" SelectionBrush="{x:Null}" FontWeight="Bold">
                    <TextBox.Template>
                        <ControlTemplate TargetType="{x:Type TextBox}">
                            <ScrollViewer Name="PART_ContentHost"/>
                        </ControlTemplate>
                    </TextBox.Template>
                </TextBox>
                <TextBox IsReadOnly="True"  x:Name="PlaceOfEvent" HorizontalAlignment="Left" Height="24" Margin="0,175,0,0" TextWrapping="Wrap" Text="{Binding Path=Where}" VerticalAlignment="Top" Width="207" Background="{x:Null}" BorderBrush="{x:Null}"  Foreground="White" FontSize="14" SelectionBrush="{x:Null}">
                    <TextBox.Template>
                        <ControlTemplate TargetType="{x:Type TextBox}">
                            <ScrollViewer Name="PART_ContentHost"/>
                        </ControlTemplate>
                    </TextBox.Template>
                </TextBox>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.Template>
        <ControlTemplate>
            <ScrollViewer>
                <ItemsPresenter />
            </ScrollViewer>
        </ControlTemplate>
    </ItemsControl.Template>
</ItemsControl>

Each Tile is bound to an TileItem which means that the Bindings which point to e.g. Category, point to the Category of an TileItem.

To increase reusability it would be possible to move the code into its own usercontrol and optionally to add DependencyPropertys for better control.

Upvotes: 1

Aousaf Rashid
Aousaf Rashid

Reputation: 5758

It is as simple as that.Firstly,what you can do is,create a UserControl with all your controls inside like TextBlocks and others.Then,decide which type of container control you want to use to hold your UserControl.Let's assume it's a grid.You can specify/set grid's column/rows for each user control.A sample :

private void addControl()
{
UserControl1 MyCon = new UserControl1;
MyGrid.Children.Add(MyCon);
Grid.SetRow(MyCon , 1); ////replace 1 with required row count
}

You can create grid rows in design time,or u can do it in code behind as well :

MyGrid.RowDefinitions.Add(new RowDefinition);

If you want to use columns instead,just apply same code but change Row/Rowdefinition with Column/ColumnDefinition

Hope this helps :)

Upvotes: 1

Related Questions