WPF: Dynamical Label addition in Grid - c#

I have to create a table to display the KEY VALUE kind of thing.
I tried the below code but messed up with overlapping output. I believe, I need to create the Grid RowDefinitions and ColumnDefinitions but not able to achieve it. Please help me.
XAML:
<Window x:Class="GrideLabel.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid Name="LabelGrid"></Grid>
</Window>
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
AddLabelDynamically();
}
private void AddLabelDynamically()
{
for (int i = 0; i < 3; i++)
{
Label nameLabel = new Label(); nameLabel.Content = "KEY :"+i.ToString();
Label dataLabel = new Label(); dataLabel.Content = "VALUE :"+i.ToString();
//I want to creatre the Seperate coloum and row to display KEY
// VALUE Pair distinctly
this.LabelGrid.Children.Add(nameLabel);
this.LabelGrid.Children.Add(dataLabel);
}
}
}

You have to define Row and Column definitions and assign Rows and Columns to child Controls. Following code does that:
private void AddLabelDynamically()
{
this.LabelGrid.ColumnDefinitions.Clear();
this.LabelGrid.RowDefinitions.Clear();
this.LabelGrid.ColumnDefinitions.Add(new ColumnDefinition());
this.LabelGrid.ColumnDefinitions.Add(new ColumnDefinition());
for (int i = 0; i < 3; i++)
{
this.LabelGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
Label nameLabel = new Label(); nameLabel.Content = "KEY :" + i.ToString();
Label dataLabel = new Label(); dataLabel.Content = "VALUE :" + i.ToString();
Grid.SetRow(nameLabel, i);
Grid.SetRow(dataLabel, i);
Grid.SetColumn(nameLabel, 0);
Grid.SetColumn(dataLabel, 1);
//I want to creatre the Seperate coloum and row to display KEY
// VALUE Pair distinctly
this.LabelGrid.Children.Add(nameLabel);
this.LabelGrid.Children.Add(dataLabel);
}
}

Related

The resource is not present in the dictionary

I'm trying to get a StaticResource at code behind but when I debug just get a empty response.
In my MainPage.xaml.cs I have:
public MainPage()
{
InitializeComponent();
/*Create buttons*/
for (int i = 0; i < 15; i++)
{
FlexGrid.Add(BtnAdd(i + 1));
}
}
private Button BtnAdd(int btnNum)
{
Button btn = new Button();
btn.Text = "TestButton " + btnNum;
btn.HorizontalOptions = LayoutOptions.Center;
btn.WidthRequest = 125;
btn.HeightRequest = 125;
//Here i get empty response
btn.BorderColor = Resources["Secondary"] as Color;
btn.BorderWidth = 5;
btn.Margin = new Thickness(5, 5, 5, 5);
btn.Clicked += TestBTN;
return btn;
}
When debug says:
System.Collections.Generic.KeyNotFoundException: 'The resource 'Secondary' is not present in the dictionary
The element is in the folder Resources/Styles/colors.xaml and is a ResourceDictionary in front i can take it just with {StaticResource Secondary}.
How can I get it at code behind?
EDIT
I find this question and I try the answers, the static class answer I don't know why I need a VisualElement and the others give me this:
var rd = App.Current.Resources.MergedDictionaries.First();
So now I take the Resource dictionary but with null response of each element.
a workaround is to make a local Resource which has a BasedOn property on the global style
<ContentPage.Resources>
<Style x:Key="button_icons_style_LOCAL" TargetType="Button" BasedOn="{x:StaticResource button_icons_style}"/>
</ContentPage.Resources>
and then to use the local one in the code.
new Button() { Style = (Style)Resources["button_icons_style_LOCAL"] }

Programmatically generated expanders with scrollable content resizing to window

I'm looking for a better way to make a programmatically generated window with the following behavior.
That is: List of expanders that can be initialized programmatically, each of which contains scrollable content larger than can be displayed in the window (in this case a datagrid). When an expander is expanded it's contents are limited to the available size of the window, while allowing all the rest of the expanders to be seen and manipulated. Additionally only one expander can be open at any given time.
This functionality seems that it could be useful in a lot of application menus, so I was surprised how difficult it was to implement. Is there a better way than what I did?
XAML (surprisingly simple):
<Window x:Class="ExpanderScrollExample.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"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="200">
<Grid>
<ItemsControl ItemsSource="{Binding Dict}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid Loaded="GridLoaded" ScrollViewer.CanContentScroll="False"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Header ="{Binding Key, Mode=OneWay}" Expanded="Expander_Expanded" Collapsed="Expander_Collapsed">
<DataGrid ItemsSource="{Binding Path=Value}"/>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Code Behind (with most of the magic):
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ExpanderScrollExample
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
private void GridLoaded(object sender, RoutedEventArgs e)
{
Grid grid = sender as Grid;
grid.LayoutUpdated += (s, e2) =>
{
var childCount = grid.Children.Count;
int rowsToAdd = (childCount - grid.RowDefinitions.Count);
for (int row = 0; row < rowsToAdd; row++)
{
RowDefinition rowDefinition = new RowDefinition();
rowDefinition.Height = new GridLength(0, GridUnitType.Auto);
grid.RowDefinitions.Add(rowDefinition);
}
for (int i = 0; i < childCount; i++)
{
var child = grid.Children[i] as FrameworkElement;
Grid.SetRow(child, i);
}
};
}
private void Expander_Expanded(object sender, RoutedEventArgs e)
{
ContentPresenter parentDataContext = VisualTreeHelper.GetParent(sender as DependencyObject) as ContentPresenter;
Grid grid = VisualTreeHelper.GetParent(parentDataContext as DependencyObject) as Grid;
grid.RowDefinitions[Grid.GetRow(parentDataContext)].Height = new GridLength(1, GridUnitType.Star);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(grid); i++)
{
DependencyObject neighborDataContext = VisualTreeHelper.GetChild(grid, i);
for (int j = 0; j < VisualTreeHelper.GetChildrenCount(neighborDataContext); j++)
{
DependencyObject neighborExpander = VisualTreeHelper.GetChild(neighborDataContext, j);
if (neighborExpander is Expander && neighborExpander != sender)
{
((Expander)neighborExpander).IsExpanded = false;
this.Collapse(neighborExpander as Expander);
}
}
}
}
private void Expander_Collapsed(object sender, RoutedEventArgs e)
{
this.Collapse(sender as Expander);
}
private void Collapse(Expander expander)
{
ContentPresenter parent = VisualTreeHelper.GetParent(expander as DependencyObject) as ContentPresenter;
Grid grandparent = VisualTreeHelper.GetParent(parent as DependencyObject) as Grid;
grandparent.RowDefinitions[Grid.GetRow(parent)].Height = new GridLength(0, GridUnitType.Auto);
}
}
}
ViewModel (only for data generation)
using System.Collections.Generic;
namespace ExpanderScrollExample
{
class MainWindowViewModel
{
public Dictionary<string, List<MyClass>> Dict { get; }
public MainWindowViewModel()
{
Dict = new Dictionary<string, List<MyClass>>();
for ( int i = 0; i < 5; i++ )
{
string key = "Header " + i.ToString();
Dict[key] = new List<MyClass>();
for ( int j = 0; j < 100; j++)
{
Dict[key].Add(new MyClass(j, i*100 + j));
}
}
}
public class MyClass
{
public int Column1 {get; set;}
public int Column2 { get; set; }
public MyClass( int column1, int column2)
{
Column1 = column1;
Column2 = column2;
}
}
}
}
I also typically try to confirm with MVVM pattern, but was unable to do so in this case.
Generating items controls in grid is taken from here.
I also considered using style triggers to expand/collapse and set size as described in this answer but I couldn't think of a good way how to bind the dynamically generated rows with expanders.
Is there a better way to do this?
1. Only one Expander expanded at a given time
To make sure that only one Expander is expanded at a given time, you can create an Attached Behavior. I implemented one for reference.
ExpanderGroupBehavior (inspired by RadioButton)
<Expander wpf:ExpanderGroupBehavior.GroupName="ExpanderGroup01" />
This makes sure that only one Expander within the same group is expanded.
2. Expanded Expander fill available space
To achieve that, you may create your own Panel which handles that for you.
See How to get controls in WPF to fill available space? and https://learn.microsoft.com/en-us/dotnet/framework/wpf/controls/how-to-create-a-custom-panel-element

Create StackPanels dynamically and refer to any of them in code behind

In its simplest form...
I would like to create as many StackPanels as I want and then add Rectangles in them. Then to be able to change the Fill color of any one of the Rectangles when I click the Start Button for instance. All in Code Behind.
Any help would be appreciated.
For example, if our favorite beer wrote the framework I could do it like this:
XAML:
<Page
x:Class="Test2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Test2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Name="StartButton" Content="Start" Click="StartButton_Click" Height="30" Width="200" Margin="5"/>
</StackPanel>
<StackPanel Grid.Row="1" Name="myStackPanel" VerticalAlignment="Top"/>
</Grid>
</Page>
Code Behind:
namespace Test2
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
for (var i = 0; i < 5; i++) // The 5 here could be any number
{
myStackPanel.Children.Add(new StackPanel
{
Name = "myPanel" + i,
Orientation = Orientation.Horizontal
});
for (var j = 0; j < 10; j++) // The 10 here could be any number
{
("myPanel" + i).Children.Add(new Rectangle
{
Name = "myRectangle" + i + "-" + j,
Fill = new SolidColorBrush(Colors.Black),
Width = 20,
Height = 20,
Margin = new Thickness(1)
});
}
}
}
private void StartButton_Click(object sender, RoutedEventArgs e)
{
// E.G. To change the Fill color of Rectangle4 in StackPanel2
("myRectangle" + 2 + "-" + 4).Fill = new SolidColorBrush(Colors.Red);
}
}
}
Firstly, to add Rectangle shapes, we can create an instance of StackPanel and manipulate its Children elements:
for (var i = 0; i < 5; i++) // The 5 here could be any number
{
StackPanel sp = new StackPanel
{
Name = "myPanel" + i,
Orientation = Orientation.Horizontal
};
myStackPanel.Children.Add(sp);
for (var j = 0; j < 10; j++) // The 10 here could be any number
{
sp.Children.Add(new Rectangle
{
Name = "myRectangle" + i + "-" + j,
Fill = new SolidColorBrush(Colors.Black),
Width = 20,
Height = 20,
Margin = new Thickness(1)
});
}
}
Then to be able to change the Fill color of any one of the Rectangles when I click the Start Button for instance. All in Code Behind.
As tgpdyk mentioned, we need to use VisualTreeHelper to find the specified rectangle shape.
Helper class:
public static class FrameworkElementExtensions
{
public static T TraverseCTFindShape<T>(DependencyObject root, String name) where T : Windows.UI.Xaml.Shapes.Shape
{
T control = null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++)
{
var child = VisualTreeHelper.GetChild(root, i);
string childName = child.GetValue(FrameworkElement.NameProperty) as string;
control = child as T;
if (childName == name)
{
return control;
}
else
{
control = TraverseCTFindShape<T>(child, name);
if (control != null)
{
return control;
}
}
}
return control;
}
}
How to use it:
private void StartButton_Click(object sender, RoutedEventArgs e)
{
// E.G. To change the Fill color of Rectangle4 in StackPanel2
var rec = FrameworkElementExtensions.TraverseCTFindShape<Shape>(myStackPanel, "myRectangle" + 2 + "-" + 4);
rec.Fill = new SolidColorBrush(Colors.Red);
}
I've uploaded my sample to Github repository
That is not how you approach this in WPF, at all.
You usually do not concern yourself with any UI components but only the data. In this case you data bind an ItemsControl to a list of rows, each row containing a list of cells. In the ItemsControl definition you then set an ItemTemplate that contains another ItemsControl binding to the cells. In the nested ItemsControl you then can set an ItemTemplate where you bind the Background to a (notifying) property of your cells which you then just need to change in code.
Check out these overviews:
Data Binding Overview
Data Templating Overview
You may also want to look into the Model-View-ViewModel pattern to ensure a suitable application architecture.

How to get button name added on UniformGrid runtime?

Get Button name added on UniformGrid during run time.
My code is:
for (int i = 1; i <= no; ++i)
{
Button button = new Button()
{
Content = i,
Tag = i,
Background = Brushes.White,
Height = 30,
Width = 30,
Name="A" + i.ToString()
};
string name=button.Name;
button.Click += new RoutedEventHandler(button_Click);
this.grid1.Children.Add(button);
if (name.Equals("A1"))
{
button.Background = Brushes.White;
}
}
My requirement is to get Button name in other function:
private void Sum()
{
}
Here is my XAML:
<UniformGrid x:Name="grid1" Margin="816,115,96,354" Grid.Column="1" />
It isn't clear what is the ultimate goal here. But you can loop through all child of UniformGrid which type is Button to get name of each button and take action accordingly :
foreach (var btn in grid.Children.OfType<Button>())
{
var btnName = btn.Name;
//take action according to button name
}

How to use C# to setup UI with XAML Grid

Context: Windows 8.1 windows store app codeing in C# and XAML. Using Visual Studio Express 2013.
Hi, I've been trying to code XAML app for windows store, but I just can't seem to linking the XAML control (Page, Grid, ContentPresenter, or whatever) to the c# code properly.
This has caused much frustration and I can't seem to find anything that answers this problem explicitly and directly.
So I'm asking for help with a very specific context:
I'm trying to build the xaml UI in c# to be executed when the app starts, but I can't seem to reference the control properly to GIVE my UI, or the line execution order is wrong, I just don't know.
I have a Page in my Main.xaml code:
<Page
x:Class="App4.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App4"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
>
<Grid x:Name="pgMain"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
>
</Grid>
I tried alternating x:Name with Name too...
Anyway, the C# code is this part, and here I am trying to set the Grid "pgMain" to the Grid object I create using my custom method.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
namespace App4 {
public sealed partial class MainPage : Page
{
public MainPage() {
//this right here. is the line order wrong? Tried that.
//Is it impossible to do what I want before/after InitializeComponent?
//If so, then is there another way to achieve this???
pgMain = buildUIGrid();
this.InitializeComponent();
}
public Grid buildUIGrid() {
Grid g = new Grid();
int[] dimensions = { 3, 3 };//grid is 3 by 3
//setup grid definitions
for (int i = 0; i < dimensions[0]; i++)
g.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Star) });
for (int i = 0; i < dimensions[1]; i++)
g.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(0, GridUnitType.Star) });
//setup testing textboxes
for (int i = 0; i < dimensions[0]; i++) {
for (int j = 0; j < dimensions[1]; j++){
TextBlock tb = new TextBlock();
tb.Text = "test at col "+i+", row "+j;
Grid.SetColumn(tb, i);
Grid.SetRow(tb, j);
g.Children.Add(tb);
}
}
return g;
}
}
}
Alternatively, try this method for a single 200x200 grid with 1 textblock
public Grid buildUIGrid() {
Grid g = new Grid();
g.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(200, GridUnitType.Pixel) });
g.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(200, GridUnitType.Pixel) });
int i, j; i = 0; j = 0;
TextBlock tb = new TextBlock();
tb.Text = "test at col "+i+", row "+j;
Grid.SetColumn(tb, i);
Grid.SetRow(tb, j);
g.Children.Add(tb);
return g;
}
Please do not generalize, just answer to the given scenario.
Your problem is in the constructor:
public MainPage() {
//this right here. is the line order wrong? Tried that.
//Is it impossible to do what I want before/after InitializeComponent?
//If so, then is there another way to achieve this???
pgMain = buildUIGrid();
this.InitializeComponent();
}
The XAML file is parsed in the InitializeComponent method (look in your project folders for a file name MainPage.i.g.cs and you'll see that pgMain is set in that method). Before that pgMain is null.
Just move pgMain = buildUIGrid below InitializeComponent:
public MainPage() {
this.InitializeComponent();
buildUIGrid(pgMain);
}
Also update buildUIGrid:
public void buildUIGrid(Grid g) {
g.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(200, GridUnitType.Pixel) });
g.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(200, GridUnitType.Pixel) });
int i, j; i = 0; j = 0;
TextBlock tb = new TextBlock();
tb.Text = "test at col "+i+", row "+j;
Grid.SetColumn(tb, i);
Grid.SetRow(tb, j);
g.Children.Add(tb);
}

Categories