Question Dynamically Creating Textblocks and Grids

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
88
Programming Experience
Beginner
This might seem pointless but I'd like to know if this is possible and practice doing it.

I want to type into a text box and have the text mirrored onto the form, say below the textbox. I don't want to bind a textblock to a string property even though that will do exactly as I just described. I want the word that is mirrored onto the form to consists of an individual textblock in an individual grid column for each letter, this would go into a cell on the main grid, likely centered in the form below the textbox.

So if I type the word "Hello" into the text box a five column grid would be created, in each column would be one textblock showing a single letter of the word "Hello". It's hard for me to think about attempting this because I don't know how this can be done without explicitly creating and naming the controls. I imagine using a "foreach (char c in stringName)" loop, each time a column is made, a textblock is made, added to the grid and changed to the appropriate letter but maybe I'm thinking about that wrong? How would I do this?

Thanks
 

Sheepings

Senior Programmer
Joined
Sep 5, 2018
Messages
844
Location
UK
Programming Experience
10+
Fragment elements such as Textblocks do not consist of the functionality of that of a textbox control. So therefore, because the functionality is a bit different, there is no TextChanged event handler for the the fragment element textblocks but there is for the textbox control. How to: Detect When Text in a TextBox Has Changed - WPF

Detecting when you type in "H", you would build up a variable of the keys being inputed, and extend the variable as new keys are typed. Additionally checking for key presses such as the space bar and back and return keys are pressed to remove letters/chars from the variable as they are typed.

For each new char, you would construct your Grid.ColumnDefinitions along with your Grid.RowDefinitions respectively, just as you've done in your previous topic : Question - Can a wrap panels children be aligned with each other? and add your controls to the new section of the grid.
 
Last edited:

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
88
Programming Experience
Beginner
Thanks. Since the only file that the grid can be referenced from is the c# file for my main view, that would be the place to code this new grid right?
 

Skydiver

Well-known member
Joined
Apr 6, 2019
Messages
922
Location
Virginia Beach, VA
Programming Experience
10+
I know that you wanted to use a grid, but here's my quick and dirty implementation using a horizontal stack panel:
Screenshot_4.png


The code was pretty simple. No explicitly named controls. Everything using WPF MVVM goodness.
MainWindow.xaml:
<Window x:Class="WpfBanner.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:WpfBanner"
        mc:Ignorable="d"
        Title="WPF Banner" Height="450" Width="800">
    <StackPanel>
        <TextBox Text="{Binding Path=Text,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
        <ItemsControl ItemsSource="{Binding Chars}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"
                                HorizontalAlignment="Center"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border BorderThickness="1"
                            BorderBrush="Black"
                            Width="50">
                        <TextBlock Text="{Binding}"
                                   FontSize="20"
                                   Margin="10"
                                   HorizontalAlignment="Center"
                                   VerticalAlignment="Bottom"/>
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>    </StackPanel>
</Window>
MainWindow.xaml.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.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfBanner
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            var banner = new Banner() { Text = "Hello, World" };
            DataContext = new BannerViewModel(banner);
            InitializeComponent();
        }
    }
}
BannerViewModel.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;

namespace WpfBanner
{
    class BannerViewModel : INotifyPropertyChanged
    {
        readonly Banner _banner;

        public string Text
        {
            get => _banner.Text;

            set
            {
                _banner.Text = value;
                NotifyPropertyChanged();
                NotifyPropertyChanged("Chars");
            }
        }

        public List<string> Chars => Text.Select(c => c.ToString()).ToList();

        public BannerViewModel(Banner banner)
            => _banner = banner;

        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged([CallerMemberName] string info = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
    }
}
Banner.cs:
using System;
using System.Collections.Generic;
using System.Text;

namespace WpfBanner
{
    class Banner
    {
        public string Text { get; set; }
    }
}
 

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
88
Programming Experience
Beginner
Skydiver that is an excellent and very helpful example thank you for writing that out. And a stackpanel is absolutely fine. Now I have to bite the bullet and learn about this ItemsControl and DataTemplate stuff.
 

Skydiver

Well-known member
Joined
Apr 6, 2019
Messages
922
Location
Virginia Beach, VA
Programming Experience
10+
All the magic is in the view model. The view model presents both the text and a list of strings where each string is a letter of the text. Each time the textbox tells the view model to update the text, the view model updates both the text and the list of strings.
 

Skydiver

Well-known member
Joined
Apr 6, 2019
Messages
922
Location
Virginia Beach, VA
Programming Experience
10+
Think of an ItemsControl as the base class for a ListBox. A ListBox is just a vertical StackPanel that presents its items as TextBlocks. So the ItemsPanelTemplate would be a vertical StackPanel, and the DataTemplate would be TextBlock.
 

Sheepings

Senior Programmer
Joined
Sep 5, 2018
Messages
844
Location
UK
Programming Experience
10+
I'm just glad Skydiver took my advice and avoided a grid. Otherwise we'd likely have another topic just like your last where you were both hung up on knowing it can be done, but knowing that it would take more code to write than either wanted to do. I've yet to put an example up for doing this in the grid, but for this example, that's pretty nice. I also like that you used the NotifyPropertyChanged, which most people now a days don't bother with. Good job Skydiver!
 

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
88
Programming Experience
Beginner
I would have gone for a stackpanel in the first place but I thought I'd be advised to use a grid instead lol.

Could you explain what line 23 in your MainWindow.xaml does. Text="{Binding}" doesn't bind to anything, what 's happening there?
 

Skydiver

Well-known member
Joined
Apr 6, 2019
Messages
922
Location
Virginia Beach, VA
Programming Experience
10+
On line 11, there is this code which tells the ItemsControl that the objects will be bound to the Chars property of the DataContext:
Code:
<ItemsControl ItemsSource="{Binding Chars}">
So when it's time to create each item, each object from the Chars list is passed on to an instance of the DataTemplate for binding. Since Chars is a List<string>, that means that each object passed on is just a string. (See my response to your other thread where I said you can just bind to a string.) By saying {Binding}, I am telling WPF to bind to the entire object, is this case the string. (See Specifying path to the value)
 

Sheepings

Senior Programmer
Joined
Sep 5, 2018
Messages
844
Location
UK
Programming Experience
10+
I would have gone for a stackpanel in the first place but I thought I'd be advised to use a grid instead lol.
Lol! Well you wanted to learn Xaml, and WPF at the time, and I thought that would be a better way for you to start, by learning the Xaml back-end, and what you can do with different controls using only Xmal. I also thought the Grid was a better example for you to do what you were trying to do, and it still is as a viable control for what you were doing; as I've shown a few examples on that other topic of how it can be done with a grid alone including a youtube tutorial, I just didn't have the time to write it out myself at the time. The thing is that its very easy to use alignment panels (stack panels alike), but if you ever work for a customer as a developer, they can insist that no such control ever be used in their application.

So how would you go about doing what you need to do without being allowed use any alignment panel? Almost every developer has found themselves in this situation at some time. And It's not a nice situation to be in especially when you choose basic assignments your whole life to test yourself with controls which are designed to make your life easier. The challenge herein would be to try and build something that is dependent on alignment panels but to build them without; is much more challenging and not to mention, fun! It also teaches you new skills to adapt whenever put in that situation by your future employer.

I initially started writing a Git for your other Grid related post, but I nor Skydiver have had time to update it since. Maybe someday we will revisit it soon.
 

Skydiver

Well-known member
Joined
Apr 6, 2019
Messages
922
Location
Virginia Beach, VA
Programming Experience
10+
And here's basically the same code above, but displaying as a Grid instead of a StackPanel. Just the modified/new code below:

MainWindow.xaml:
<Window x:Class="WpfBanner.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:WpfBanner"
        mc:Ignorable="d"
        Title="WPF Banner" Height="450" Width="800">
    <StackPanel>
        <TextBox Text="{Binding Path=Text,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
        <ItemsControl ItemsSource="{Binding Chars}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Grid local:GridHelpers.ColumnCount="{Binding Chars.Count}"
                          local:GridHelpers.RowCount="1"
                          HorizontalAlignment="Center"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border BorderThickness="1"
                            BorderBrush="Black"
                            Width="50">
                        <TextBlock Text="{Binding}"
                                   FontSize="20"
                                   Margin="10"
                                   HorizontalAlignment="Center"
                                   VerticalAlignment="Bottom"/>
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>
</Window>
GridHelpers.cs:
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace WpfBanner
{
    /// <summary>
    /// Adapted from: https://rachel53461.wordpress.com/2011/09/17/wpf-grids-rowcolumn-count-properties/
    /// </summary>
    public static class GridHelpers
    {
        #region RowCount Property

        /// <summary>
        /// Adds the specified number of Rows to RowDefinitions.
        /// Default Height is Auto
        /// </summary>
        public static readonly DependencyProperty RowCountProperty =
            DependencyProperty.RegisterAttached(
                "RowCount", typeof(int), typeof(GridHelpers),
                new PropertyMetadata(-1, RowCountChanged));

        public static int GetRowCount(DependencyObject obj)
            => (int)obj.GetValue(RowCountProperty);

        public static void SetRowCount(DependencyObject obj, int value)
            => obj.SetValue(RowCountProperty, value);

        public static void RowCountChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid grid) || (int)e.NewValue < 0)
                return;

            grid.RowDefinitions.Clear();

            for (int i = 0; i < (int)e.NewValue; i++)
                grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });

            SetStarRows(grid);

            RegisterForLayoutUpdate(grid);
        }

        #endregion

        #region ColumnCount Property

        /// <summary>
        /// Adds the specified number of Columns to ColumnDefinitions.
        /// Default Width is Auto
        /// </summary>
        public static readonly DependencyProperty ColumnCountProperty =
            DependencyProperty.RegisterAttached(
                "ColumnCount", typeof(int), typeof(GridHelpers),
                new PropertyMetadata(-1, ColumnCountChanged));

        public static int GetColumnCount(DependencyObject obj)
            => (int)obj.GetValue(ColumnCountProperty);

        public static void SetColumnCount(DependencyObject obj, int value)
            => obj.SetValue(ColumnCountProperty, value);

        public static void ColumnCountChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid grid) || (int)e.NewValue < 0)
                return;

            grid.ColumnDefinitions.Clear();

            for (int i = 0; i < (int)e.NewValue; i++)
                grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });

            SetStarColumns(grid);

            RegisterForLayoutUpdate(grid);
        }

        #endregion

        #region StarRows Property

        /// <summary>
        /// Makes the specified Row's Height equal to Star.
        /// Can set on multiple Rows
        /// </summary>
        public static readonly DependencyProperty StarRowsProperty =
            DependencyProperty.RegisterAttached(
                "StarRows", typeof(string), typeof(GridHelpers),
                new PropertyMetadata(string.Empty, StarRowsChanged));

        public static string GetStarRows(DependencyObject obj)
            => (string)obj.GetValue(StarRowsProperty);

        public static void SetStarRows(DependencyObject obj, string value)
            => obj.SetValue(StarRowsProperty, value);

        public static void StarRowsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
                return;

            SetStarRows(grid);

            RegisterForLayoutUpdate(grid);
        }

        #endregion

        #region StarColumns Property

        /// <summary>
        /// Makes the specified Column's Width equal to Star.
        /// Can set on multiple Columns
        /// </summary>
        public static readonly DependencyProperty StarColumnsProperty =
            DependencyProperty.RegisterAttached(
                "StarColumns", typeof(string), typeof(GridHelpers),
                new PropertyMetadata(string.Empty, StarColumnsChanged));

        public static string GetStarColumns(DependencyObject obj)
            => (string)obj.GetValue(StarColumnsProperty);

        public static void SetStarColumns(DependencyObject obj, string value)
            => obj.SetValue(StarColumnsProperty, value);

        public static void StarColumnsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
                return;

            SetStarColumns(grid);

            RegisterForLayoutUpdate(grid);
        }

        #endregion

        static void SetStarColumns(Grid grid)
        {
            string[] starColumns = GetStarColumns(grid).Split(',');

            for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
            {
                if (starColumns.Contains(i.ToString()))
                    grid.ColumnDefinitions[i].Width = new GridLength(1, GridUnitType.Star);
            }
        }

        static void SetStarRows(Grid grid)
        {
            string[] starRows = GetStarRows(grid).Split(',');

            for (int i = 0; i < grid.RowDefinitions.Count; i++)
            {
                if (starRows.Contains(i.ToString()))
                    grid.RowDefinitions[i].Height = new GridLength(1, GridUnitType.Star);
            }
        }

        static HashSet<Grid> _registered = new HashSet<Grid>();

        static void RegisterForLayoutUpdate(Grid grid)
        {
            if (_registered.Contains(grid))
                return;

            _registered.Add(grid);

            grid.LayoutUpdated += (o, e) =>
            {
                int columnCount = grid.ColumnDefinitions.Count;
                int count = 0;
                foreach (var presenter in grid.Children.OfType<ContentPresenter>())
                {
                    Grid.SetColumn(presenter, count % columnCount);
                    Grid.SetRow(presenter, count / columnCount);
                    count++;
                }
            };
        }
    }
}
Basically I took Rachel Lim's GridHelper.cs and the tweaked it to also set the grid positions of the child elements as outlined by Colin Eberhardt in how to make the Grid play nicely with the ItemsControl. In my implementation above, I'm not using a Phantom panel. I'm assuming that each DataTemplate is truly mean to live in its own grid cell. The distribution of the children among the grid cells is dependent on the ColumnCount.
 
Last edited:

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
88
Programming Experience
Beginner
Lol! Well you wanted to learn Xaml, and WPF at the time, and I thought that would be a better way for you to start, by learning the Xaml back-end, and what you can do with different controls using only Xmal. I also thought the Grid was a better example for you to do what you were trying to do, and it still is as a viable control for what you were doing; as I've shown a few examples on that other topic of how it can be done with a grid alone including a youtube tutorial, I just didn't have the time to write it out myself at the time. The thing is that its very easy to use alignment panels (stack panels alike), but if you ever work for a customer as a developer, they can insist that no such control ever be used in their application.

So how would you go about doing what you need to do without being allowed use any alignment panel? Almost every developer has found themselves in this situation at some time. And It's not a nice situation to be in especially when you choose basic assignments your whole life to test yourself with controls which are designed to make your life easier. The challenge herein would be to try and build something that is dependent on alignment panels but to build them without; is much more challenging and not to mention, fun! It also teaches you new skills to adapt whenever put in that situation by your future employer.

I initially started writing a Git for your other Grid related post, but I nor Skydiver have had time to update it since. Maybe someday we will revisit it soon.
No worries, take all the time you need, there are plenty of other topics for me to tear my hair out over :) Maybe it's even a bit too advanced of a topic for me at this stage anyway.

Skydiver, your example with the grid is WAY more complex, I think I'm gonna stick to mulling over the first one. I very much appreciate the time you put into it, but there's too much there I don't understand. I'll come back to that one day, I've no doubt there is a lot of valuable programming knowledge in there that I'm just incapable of recognizing yet.

Edit: One more thing, I've been trying to google for more information on using binding with no specified property to bind to, since I don't know exactly what that kind of binding it's called I'm failing to find anything on it from google. I tried searching for "="{Binding}"" but even inside quotes google just searches for the word "binding". Basically I can't get to any specific information on it. What's it called so I can actually search for more info/examples regarding it?
 
Last edited:

JohnH

C# Forum Moderator
Staff member
Joined
Apr 23, 2011
Messages
609
Location
Norway
Programming Experience
10+

Sheepings

Senior Programmer
Joined
Sep 5, 2018
Messages
844
Location
UK
Programming Experience
10+
Maybe it's even a bit too advanced of a topic for me at this stage anyway.
I disagree. I think you've handled yourself quite well as a beginner to WPF, especially for someone who came from Winforms originally. And I would encourage you to research all the little bits in Skydiver's examples. Such as Interfaces, methods, properties, and anything in and of that nature. Once you understand the puzzle, you can understand how it all fits together. That may take you a while to figure out after some light reading of the relevant documentation.

Remember, we are here to help and guide you if you need help understanding something which you don't fully comprehend. So don't be afraid to ask questions, no matter how silly or simple they may sound. Of course I always encourage searching up something on the inter webs before asking a question on the forums, as this saves us linking you to something you could very well have searched the web for yourself. But if you still don't grasp something, share it with us so we can help you with it.

I didn't want to say this in fear of raising your ego. lol And I don't think I've ever given someone a public endorsement on any public forum. You see, I actually think that you could be of value to others here on these forums by helping other people with their code on their topics, providing you brush up and spend enough time learning and researching and expanding your knowledge of the subjects which you write code for today.

I know when someone has a good understanding of not just code, but the language in which they are writing and I can also tell when someone has good code ethic about them, and most importantly if they have the potential to become a good programmer down the line. And I think you have that potential. I probably inherit this intuition from when i became a project manager. You should know however, that one of the best ways to become a better programmer is to help others troubleshoot their code, and help them find solutions to their problems. The reason I am showing an interest in you; is simply because I thrive on anyone with the willingness to learn, and the fact that you've shown great interest and willingness previously with your Grid topic. I respect people who are willing to advance themselves in this field regardless if you do it seriously or just for fun. Keep at it (y)
 

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
88
Programming Experience
Beginner
Wow, thank you very much for the confidence boost! You'll find, however, that my ego leans in the direction of self-deprecation so it could do with being raised a bit :)

I come close to quitting most days, and on occasion when I have quit I'm back the next day, the drive to learn programming keeps me going, I find it thrilling and exciting! But the only reason I feel like quitting sometimes is simply because it can be difficult to know what step to take next as a self learner (especially when I'm up against something that feels impossible to understand), I have no path laid out before me and trying to scrape one together is difficult, so I tend to bounce around topics randomly. I keep making progress though, bit by bit, so I suppose I just need to stick at it, no matter how difficult things seem at times.

You seem to have more confidence in my potential then I do, I don't have much of that at all really, it just feels so good to learn this stuff, that's what drives me, I just love it.

As for Skydivers generous examples, I know I have some excellent material there to get my research going, I'm not done with them yet.
 
Top Bottom