I’m trying to create virtualizing uniform grid, so I took some code from here which creates a panel where I can set the number of columns,
and it works fine, and it’s about 3-4 times faster than standard uniform grid (I tested it on a complicated item template for listbox with lots of records).
So I thought I change the base control from Panel to VirtualizingStackPanel but as soon as I do this, no records show. Any Ideas why?
Here is my working code:
Change below to derive from VirtualizingPanel or VirtualizingStackPanel, and it won't work anymore :(
public class MyUniformGrid : Panel // VirtualizingStackPanel
{
public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(int), typeof(MyUniformGrid), new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsMeasure));
public int Columns
{
set { SetValue(ColumnsProperty, value); }
get { return (int)GetValue(ColumnsProperty); }
}
private int Rows => (InternalChildren.Count + Columns - 1) / Columns;
protected override Size MeasureOverride(Size sizeAvailable)
{
var sizeChild = new Size(sizeAvailable.Width / Columns, sizeAvailable.Height / Rows);
double maxwidth = 0;
double maxheight = 0;
foreach (UIElement child in InternalChildren)
{
child.Measure(sizeChild);
maxwidth = Math.Max(maxwidth, child.DesiredSize.Width);
maxheight = Math.Max(maxheight, child.DesiredSize.Height);
}
return new Size(Columns * maxwidth, Rows * maxheight);
}
protected override Size ArrangeOverride(Size sizeFinal)
{
var sizeChild = new Size(sizeFinal.Width / Columns, sizeFinal.Height / Rows);
for (var index = 0; index < InternalChildren.Count; index++)
{
var row = index / Columns;
var col = index % Columns;
var rectChild = new Rect(new Point(col * sizeChild.Width, row * sizeChild.Height), sizeChild);
InternalChildren[index].Arrange(rectChild);
}
return sizeFinal;
}
}
xaml
<ListBox x:Name="MyListBox">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<uniformGridDemo:MyUniformGrid Columns="2" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
code behind
public MainWindow()
{
InitializeComponent();
var list = new List<int>();
for (int i = 0; i < 1000; i++)
{
list.Add(i);
}
MyListBox.ItemsSource = list;
}
Thank you
I'm not an expert on VirtualizingStackPanel, but if you just want to resolve the item display problem, below would work.
public class MyUniformGrid : VirtualizingStackPanel
{
public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(int), typeof(MyUniformGrid), new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsMeasure));
public int Columns
{
set { SetValue(ColumnsProperty, value); }
get { return (int)GetValue(ColumnsProperty); }
}
private int Rows
{
get
{
return (InternalChildren.Count + Columns - 1) / Columns;
}
}
protected override Size MeasureOverride(Size sizeAvailable)
{
base.MeasureOverride(sizeAvailable);
var sizeChild = new Size(sizeAvailable.Width / Columns, sizeAvailable.Height / Rows);
double maxwidth = 0;
double maxheight = 0;
foreach (UIElement child in this.InternalChildren)
{
child.Measure(sizeChild);
maxwidth = Math.Max(maxwidth, child.DesiredSize.Width);
maxheight = Math.Max(maxheight, child.DesiredSize.Height);
}
return new Size(Columns * maxwidth, Rows * maxheight);
}
protected override Size ArrangeOverride(Size sizeFinal)
{
base.ArrangeOverride(sizeFinal);
var sizeChild = new Size(sizeFinal.Width / Columns, sizeFinal.Height / Rows);
for (var index = 0; index < InternalChildren.Count; index++)
{
var row = index / Columns;
var col = index % Columns;
var rectChild = new Rect(new Point(col * sizeChild.Width, row * sizeChild.Height), sizeChild);
InternalChildren[index].Arrange(rectChild);
}
return sizeFinal;
}
}
But VirtualizingStackPanel is much complex implementation then overriding MeasureOverride & ArrangeOverride methods and arranging items. you need to process the scroll info and respect to that addition & removal of items in panel needs to be handled. So even after displayed items completely you may not get the desired performance.
I had implemented something similar in Silverlight by applying styles to ListBox and replacing the ItemsPanelTemplate with a WrapPanel. I've used the same in Windows Phone and WPF projects with no issues.
Styles
<!--Wrapping ListBox Styles-->
<Style x:Key="StretchedItemContainerStyle" TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
<Style x:Key="ListBox_HorizontalWrapStyle" TargetType="ListBox">
<Setter Property="ItemContainerStyle" Value="{StaticResource StretchedItemContainerStyle}"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" Margin="0"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<ScrollViewer VerticalScrollBarVisibility="Auto" BorderBrush="{x:Null}" >
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--End Wrapping ListBox Styles-->
Xaml
<ListBox x:Name="MyListBox" Style="StaticResource ListBox_HorizontalWrapStyle">
You should also be able to style the WrapPanel for what ever other performance gains can be attained from it.
Related
I want to draw a samurai sudoku grid on my C# WPF project.
this is the example of samurai sudoku
for each TextBox within the grid, I want to load it with dynamic value from the txt file.
txt file : "23987239847239847" (in total 405 integers)
I already have the normal sudoku grid working
code:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding Dimension}" Columns="{Binding Dimension}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Text="{Binding Value, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
What are the most efficient ways to draw a grid that looks like a samurai suoku grid?
Example for demonstration.
It is not as difficult as in the picture in the question.
But it will not be a problem for you to supplement it.
public class SudokuCell
{
public Thickness Border { get; }
public int Value { get; set; }
public int Row { get; }
public int Column { get; }
public SudokuCell(int row, int column, Thickness border)
{
Row = row;
Column = column;
Border = border;
}
public SudokuCell(int row, int column, Thickness border, int value)
: this(row, column, border)
{
Value = value;
}
}
public class SudokuViewModel
{
public ObservableCollection<SudokuCell> Cells { get; }
= new ObservableCollection<SudokuCell>();
public IEnumerable<int> ValidValues { get; } = Enumerable.Range(1, 9);
private readonly SudokuCell[,] cellsArray;
private static readonly Random random = new Random();
public SudokuViewModel()
{
cellsArray = new SudokuCell[9, 9];
for (int row = 0; row < 9; row++)
{
for (int column = 0; column < 9; column++)
{
if ((row / 3 + column / 3) % 2 == 1)
continue;
double left = 0.5;
if (column % 3 == 0)
left = 3;
double top = 0.5;
if (row % 3 == 0)
top = 3;
double right = 0.5;
if (column % 3 == 2)
right = 3;
double bottom = 0.5;
if (row % 3 == 2)
bottom = 3;
int value = 0;
if (random.Next(5) < 2)
value = random.Next(9) + 1;
cellsArray[row, column] = new SudokuCell(
row,
column,
new Thickness(left, top, right, bottom),
value);
}
}
foreach (var cell in cellsArray)
{
Cells.Add(cell);
}
}
}
<Window.Resources>
<local:SudokuViewModel x:Key="viewModel"/>
<ItemsPanelTemplate x:Key="Sudoku.Panel">
<UniformGrid Columns="9" Rows="9"/>
</ItemsPanelTemplate>
<DataTemplate x:Key="Sudoku.CellTemplate" DataType="{x:Type local:SudokuCell}">
<Border BorderBrush="SkyBlue" BorderThickness="{Binding Border}"
Background="{Binding Background, ElementName=comboBox}">
<Border.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Border.Opacity" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<ComboBox x:Name="comboBox" ItemsSource="{Binding ValidValues, Source={StaticResource viewModel}}"
SelectedItem="{Binding Value}"
FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"
BorderThickness="0"/>
</Border>
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding Cells, Source={StaticResource viewModel}}"
ItemTemplate="{DynamicResource Sudoku.CellTemplate}"
ItemsPanel="{DynamicResource Sudoku.Panel}"/>
I'm trying to display a DateTime formatted like 2019-10-07 17:00 in a TextBlock. The text should be underlined and dashed. To do this I'm using the following xaml
<TextBlock Text="2019-10-07 17:00">
<TextBlock.TextDecorations>
<TextDecoration Location="Underline">
<TextDecoration.Pen>
<Pen Brush="Black">
<Pen.DashStyle>
<DashStyle Dashes="5"/>
</Pen.DashStyle>
</Pen>
</TextDecoration.Pen>
</TextDecoration>
</TextBlock.TextDecorations>
</TextBlock>
However, this produces some very unexpected results where it seems like each hyphen causes the dashed underline to restart its rendering. Notice the dash-pattern which looks almost random efter each hyphen.
If I change the "minus-sign-hyphen" to "non-breaking-hyphen" which looks very similar (- vs ‐), the rendering works as expected.
<TextBlock Text="2019‐10‐07 17:00" ...>
This buggy rendering of the dashed underline happends everytime I add a minus-sign-hyphen to the text but not with any other character that I could find. Has anyone else noticed this and does anyone have a solution? If not, what might be the reason for this weird behavior?
Given your format, the size should always be roughly the same so you could use another textblock like so and just let it overlay the other box
<TextBlock Text="This is a really lon" Foreground="Transparent" IsHitTestVisible="False">
<TextBlock.TextDecorations>
<TextDecoration Location="Underline">
<TextDecoration.Pen>
<Pen Brush="Black">
<Pen.DashStyle>
<DashStyle Dashes="5"/>
</Pen.DashStyle>
</Pen>
</TextDecoration.Pen>
</TextDecoration>
</TextBlock.TextDecorations>
</TextBlock>
<TextBlock Text="2019-10-07 17:00" />
This is probably a result of some weird dash-hack found in the WPF glyph rendering code. In the .NET source you will find the AdjustAdvanceForDisplayLayout() method and its comment:
// AdvanceHeight is used to compute the bounding box. In some case, eg. the dash
// character '-', the bounding box is computed to be empty in Display
// TextFormattingMode (because the metrics are rounded to be pixel aligned) and so the
// dash is not rendered!
Setting TextOptions.TextFormattingMode="Display" on the TextBlock will produce a slightly different artifact:
This tells us that we did indeed hit this "workaround" (see GlyphRun.cs line 1326).
So the question is if we can somehow get a third variant, without any of these artifacts. So far, I have not succeeded but I did try to find where this hyphen check occurs. It seems to happen in native code. See TextFormatterContext.cs and LoCreateContext.
I don't have an answer to why this odd behavior occurs. It looks like the dashes created by the Pen are mapped to the decorated text of the TextDecoration. This makes sense as the dashes or TextDecoration in general will automatically adjust to the e.g. font size. The minus character seems to produce a different spacing. Maybe this behavior doesn't occur when using a monospace font.
Anyway, you could create a tiled DrawingBrush and assign it to the Pen.Brush property to create the dashed line. You can play around with the DrawingBrush.ViewPort to alter the position or the length of the dashes.
The Viewport consists of four values and is actually a Rect that describes the tile's position and dimension: x, y, width, height. Bigger values for width and height create longer dashes.
The result is an even drawing of dashes and spaces:
<TextBlock Text="2019-10-07 17:00">
<TextBlock.TextDecorations>
<TextDecoration Location="Underline">
<TextDecoration.Pen>
<Pen>
<Pen.Brush>
<DrawingBrush Viewport="0,0,10,10"
ViewportUnits="Absolute"
TileMode="Tile">
<DrawingBrush.Drawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<GeometryGroup>
<RectangleGeometry Rect="0,0,5,5" />
<RectangleGeometry Rect="5,5,5,5" />
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Pen.Brush>
</Pen>
</TextDecoration.Pen>
</TextDecoration>
</TextBlock.TextDecorations>
</TextBlock>
The downside of this approach is that the size and position of the dashes is no longer adaptive to the size of the font.
In the end, we built a custom control called DashTextBlock to solve this issue. It derives from TextBox and is styled like a TextBlock with an added TextDecoration that uses a Pen with a LinearGradientBrush that is set up according to whatever what specified as "dash-properties" and the thickness of DashThickness.
To achieve this it uses the TextBox method GetRectFromCharacterIndex to figure out how to setup the LinearGradientBrush.
TextBox.GetRectFromCharacterIndex Method
Returns the rectangle for an edge of the character at the specified index.
It produces results like this
Sample usage
<StackPanel>
<controls:DashTextBlock Text="Testing DashTextBlock"
DashThickness="1"
DashColor="Blue">
<controls:DashTextBlock.DashStyle>
<DashStyle Dashes="4,4,4,4" Offset="0" />
</controls:DashTextBlock.DashStyle>
</controls:DashTextBlock>
<controls:DashTextBlock Text="Testing DashTextBlock"
Margin="0 5 0 0"
DashThickness="2"
DashColor="Orange">
<controls:DashTextBlock.DashStyle>
<DashStyle Dashes="8 4 8 4" Offset="0" />
</controls:DashTextBlock.DashStyle>
</controls:DashTextBlock>
</StackPanel>
DashTextBlock
public class DashTextBlock : TextBox
{
public static readonly DependencyProperty DashColorProperty =
DependencyProperty.Register("DashColor",
typeof(Color),
typeof(DashTextBlock),
new FrameworkPropertyMetadata(Colors.Black, OnDashColorChanged));
public static readonly DependencyProperty DashThicknessProperty =
DependencyProperty.Register("DashThickness",
typeof(double),
typeof(DashTextBlock),
new FrameworkPropertyMetadata(1.0, OnDashThicknessChanged));
public static readonly DependencyProperty DashStyleProperty =
DependencyProperty.Register("DashStyle",
typeof(DashStyle),
typeof(DashTextBlock),
new FrameworkPropertyMetadata(DashStyles.Solid, OnDashStyleChanged));
private static readonly DependencyProperty FontSizeCallbackProperty =
DependencyProperty.Register("FontSizeCallback",
typeof(double),
typeof(DashTextBlock),
new FrameworkPropertyMetadata(0.0, OnFontSizeCallbackChanged));
public static readonly DependencyProperty TextLengthProperty =
DependencyProperty.Register("TextLength",
typeof(double),
typeof(DashTextBlock),
new FrameworkPropertyMetadata(0.0));
public static readonly DependencyProperty DashEnabledProperty =
DependencyProperty.Register("DashEnabled",
typeof(bool),
typeof(DashTextBlock),
new FrameworkPropertyMetadata(true, OnDashEnabledChanged));
private static void OnDashColorChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
DashTextBlock dashTextBlock = source as DashTextBlock;
dashTextBlock.DashColorChanged();
}
private static void OnDashThicknessChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
DashTextBlock dashTextBlock = source as DashTextBlock;
dashTextBlock.DashThicknessChanged();
}
private static void OnDashStyleChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
DashTextBlock dashTextBlock = source as DashTextBlock;
dashTextBlock.DashStyleChanged();
}
private static void OnFontSizeCallbackChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
DashTextBlock dashTextBlock = source as DashTextBlock;
dashTextBlock.FontSizeChanged();
}
private static void OnDashEnabledChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
DashTextBlock dashTextBlock = source as DashTextBlock;
dashTextBlock.DashEnabledChanged();
}
private static Pen _transparentPen;
static DashTextBlock()
{
_transparentPen = new Pen(Brushes.Transparent, 0);
_transparentPen.Freeze();
DefaultStyleKeyProperty.OverrideMetadata(typeof(DashTextBlock), new FrameworkPropertyMetadata(typeof(DashTextBlock)));
}
private TextDecoration _dashDecoration = new TextDecoration();
public DashTextBlock()
{
Binding fontSizeCallbackBinding = new Binding();
fontSizeCallbackBinding.Source = this;
fontSizeCallbackBinding.Path = new PropertyPath(TextBlock.FontSizeProperty);
this.SetBinding(FontSizeCallbackProperty, fontSizeCallbackBinding);
TextChanged += DashTextBlock_TextChanged;
this.LayoutUpdated += DashTextBlock_LayoutUpdated;
}
private void DashTextBlock_LayoutUpdated(object sender, EventArgs e)
{
if (IsLoaded)
{
var textRect = GetRectFromCharacterIndex(Text.Length);
double availableWidth = textRect.Right;
if (textRect.IsEmpty == false &&
availableWidth > 0)
{
this.LayoutUpdated -= DashTextBlock_LayoutUpdated;
UpdateTextWithDashing();
}
}
}
public Color DashColor
{
get { return (Color)GetValue(DashColorProperty); }
set { SetValue(DashColorProperty, value); }
}
public double DashThickness
{
get { return (double)GetValue(DashThicknessProperty); }
set { SetValue(DashThicknessProperty, value); }
}
public DashStyle DashStyle
{
get { return (DashStyle)GetValue(DashStyleProperty); }
set { SetValue(DashStyleProperty, value); }
}
private double FontSizeCallback
{
get { return (double)GetValue(FontSizeCallbackProperty); }
set { SetValue(FontSizeCallbackProperty, value); }
}
public double TextLength
{
get { return (double)GetValue(TextLengthProperty); }
set { SetValue(TextLengthProperty, value); }
}
public bool DashEnabled
{
get { return (bool)GetValue(DashEnabledProperty); }
set { SetValue(DashEnabledProperty, value); }
}
private void DashTextBlock_TextChanged(object sender, TextChangedEventArgs e)
{
UpdateTextWithDashing();
}
private void FontSizeChanged()
{
//UpdateTextWithDashing();
}
private void DashEnabledChanged()
{
UpdateTextWithDashing();
}
private void DashColorChanged()
{
UpdateTextWithDashing();
}
private void DashStyleChanged()
{
UpdateTextWithDashing();
}
private void DashThicknessChanged()
{
UpdateTextWithDashing();
}
public void UpdateTextWithDashing()
{
AddDashDecoration();
_dashDecoration.Pen = CreatePenFromProperties();
}
private Pen CreatePenFromProperties()
{
if (!DashEnabled)
{
return _transparentPen;
}
if (DashStyle.Dashes.Count < 2 ||
IsLoaded == false ||
Text.Length == 0)
{
return new Pen(new SolidColorBrush(DashColor), DashThickness);
}
double length = 0.0;
foreach (var dash in DashStyle.Dashes)
{
length += dash;
}
double stepLength = 1.0 / length;
TextBox textBox = this as TextBox;
Rect textRect = Rect.Empty;
for (int l = (textBox.Text.Length - 1); l >= 0; l--)
{
if (textBox.Text[l] != ' ')
{
try
{
textRect = textBox.GetRectFromCharacterIndex(l + 1);
}
catch
{
// See possible bug here:
// https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/VirtualizingStackPanel.cs,8060
// TODO: Revisit after migrate to .NET 5
}
break;
}
}
double target = FontSize;
double availableWidth = textRect.Right;
if (textRect.IsEmpty == false &&
availableWidth > 0)
{
TextLength = availableWidth;
double current = 0;
bool count = true;
bool foundTargetLength = false;
double savedDashes = 0.0;
while (!foundTargetLength)
{
for (int i = 0; i < DashStyle.Dashes.Count; i++)
{
var dash = DashStyle.Dashes[i];
savedDashes += dash;
double increase = (target * (dash * stepLength));
double preDiff = availableWidth - current;
current += increase;
double postDiff = current - availableWidth;
if (current > availableWidth)
{
if (!count)
{
if (postDiff < preDiff || Text.Length <= 2)
{
if ((i + 1) < DashStyle.Dashes.Count)
{
savedDashes += DashStyle.Dashes[i + 1];
}
else
{
savedDashes += DashStyle.Dashes[0];
}
}
else
{
if (i == 0)
{
savedDashes -= DashStyle.Dashes.Last();
}
else
{
savedDashes -= DashStyle.Dashes[i - 1];
}
}
}
foundTargetLength = true;
target = availableWidth / (savedDashes * stepLength);
break;
}
count = !count;
}
}
}
LinearGradientBrush dashBrush = new LinearGradientBrush();
dashBrush.StartPoint = new Point(0, 0);
dashBrush.EndPoint = new Point(target, 0);
dashBrush.MappingMode = BrushMappingMode.Absolute;
dashBrush.SpreadMethod = GradientSpreadMethod.Repeat;
double offset = 0.0;
bool isFill = true;
foreach (var dash in DashStyle.Dashes)
{
GradientStop gradientStop = new GradientStop();
gradientStop.Offset = offset;
gradientStop.Color = isFill ? DashColor : Colors.Transparent;
dashBrush.GradientStops.Add(gradientStop);
offset += (dash * stepLength);
gradientStop = new GradientStop();
gradientStop.Offset = offset;
gradientStop.Color = isFill ? DashColor : Colors.Transparent;
dashBrush.GradientStops.Add(gradientStop);
isFill = !isFill;
}
Pen dashPen = new Pen(dashBrush, DashThickness);
return dashPen;
}
private void AddDashDecoration()
{
foreach (TextDecoration textDecoration in TextDecorations)
{
if (textDecoration == _dashDecoration)
{
return;
}
}
TextDecorations.Add(_dashDecoration);
}
}
Style
<Style TargetType="{x:Type controls:DashTextBlock}">
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:DashTextBlock}">
<Border x:Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="0"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="True">
<ScrollViewer x:Name="PART_ContentHost"
Focusable="False"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" TargetName="border" Value="0.56"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I'm trying to create a Nonogram (aka PuzzleCross) puzzle grid in C#/WPF, and have created two UserControls to contain the row and column keys. Each UserControl consists of a Border containing a TextBlock, with a DependencyProperty named TextControl to make the Text property accessible outside of the UserControl. Everything works fine except that the text isn't actually displayed when run. The TextControl contains the correct text, as tested with a MouseDown event and a MessageBox, but for some reason the text just isn't there.
Can anyone help me figure out what I'm missing? I have a feeling it's a simple thing, but I'm just not seeing it.
Horizontal UserControl:
<Border BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Center" Height="10" Width="100">
<TextBlock Text="{Binding ElementName=HorizontalRowLabel, Path=TextContent}" Foreground="Black" FontSize="6" MouseDown="TextBlock_MouseDown"/>
</Border>
Horizontal C#:
public partial class HorizontalRowLabel : UserControl
{
public static readonly DependencyProperty TextContentProperty = DependencyProperty.Register("TextContent", typeof(string),
typeof(HorizontalRowLabel), new FrameworkPropertyMetadata(""));
public string TextContent
{
get { return (string)GetValue(TextContentProperty); }
set { SetValue(TextContentProperty, value); }
}
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show(TextContent);
}
public HorizontalRowLabel()
{
InitializeComponent();
}
}
//Adds text HorizontalRowLabel UserControl, then adds HRL to Grid.
public void InitRowKeys(Grid puzzle)
{
for(int i = 0; i < HorizontalKeys.Length; i++)
{
RowDefinition row = new RowDefinition();
HorizontalRowLabel hrow = new HorizontalRowLabel();
row.Height = new GridLength(10);
for(int j = 0; j < HorizontalKeys[i].Length; j++)
{
if(HorizontalKeys[i].Length == 0 || j == HorizontalKeys[i].Length - 1)
{
hrow.TextContent += HorizontalKeys[i][j].ToString();
hrow.Foreground = Brushes.Black;
hrow.SetValue(Grid.RowProperty, i);
hrow.SetValue(Grid.ColumnProperty, 0);
hrow.FontSize = 6;
hrow.HorizontalAlignment = HorizontalAlignment.Right;
hrow.VerticalAlignment = VerticalAlignment.Center;
}
else
{
hrow.TextContent += HorizontalKeys[i][j].ToString() + " ";
hrow.SetValue(Grid.RowProperty, i);
hrow.SetValue(Grid.ColumnProperty, 0);
hrow.FontSize = 6;
hrow.HorizontalAlignment = HorizontalAlignment.Right;
hrow.VerticalAlignment = VerticalAlignment.Center;
}
}
//puzzle.Margin = new Thickness(0,50,0,0);
hrow.Width = 100;
hrow.Height = 30;
puzzle.RowDefinitions.Add(row);
puzzle.Children.Add(hrow);
}
}
A Binding like
Text="{Binding ElementName=HorizontalRowLabel, Path=TextContent}"
only works if you have assigned the x:Name attribute to the UserControl:
<UserControl ... x:Name="HorizontalRowLabel">
...
</UserControl>
That is however not necessary with a RelativeSource Binding:
Text="{Binding TextContent, RelativeSource={RelativeSource AncestorType=UserControl}}"
I have to do some sort of game with WPF App that contain some matrix of color boxes (ex. 10x10). On-click at some it must eliminate itself and surrounding boxes with the same color if there are more than 3, and after elimination these boxes grant some random color.
I'm fairly new in WPF apps, but I have some knowledge of C# Programming and I can't figure out from where I should start. Most difficult part for me is "spawning" this boxes and use it like a matrix.
So far I found some project that I thought it will help me, but not really.
Can someone navigate from where I can start and which is most relevant way to do this.
Thank you.
ItemsControl + UniformGrid as a Panel is a good choice to display a matrix
view
<ItemsControl Name="Board">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate >
<UniformGrid Rows="10" Columns="10"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="Transparent"
BorderBrush="Black"
BorderThickness="1"
MouseDown="CellClick"
Margin="2"
Tag="{Binding}">
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
code-behind
public partial class MainWindow : Window
{
List<Point> _board;
public MainWindow()
{
InitializeComponent();
int rows = 10;
int columns = 10;
_board = new List<Point>();
for(int r = 0; r<rows; r++)
for (int c = 0; c < columns; c++)
_board.Add(new Point(r, c));
Board.ItemsSource = _board;
}
private void CellClick(object sender, MouseButtonEventArgs e)
{
var border = (Border)sender;
var point = (Point) border.Tag;
}
}
you can create and use more complex type instead of Point and improve ItemTemplate to continue development. current ItemTemplate is nothing more that a rectangle
I used code-behind for demonstration, but in wpf MVVM in a preferred approach
EDIT extended example
in most cases you don't have to work with UI elements directly
to support different Colors I will create a custom class
public class MatrixElement
{
private string _color;
public MatrixElement(int x, int y)
{
X = x;
Y = y;
}
public int X { get; private set; }
public int Y { get; private set; }
public string Color
{
get { return _color; }
set
{
_color = value;
if (ColorChanged != null)
ColorChanged(this, EventArgs.Empty);
}
}
public event EventHandler ColorChanged;
}
window code has changed accordingly
List<MatrixElement> _board;
public MainWindow()
{
InitializeComponent();
int rows = 10;
int columns = 10;
_board = new List<MatrixElement>();
for (int r = 0; r < rows; r++)
for (int c = 0; c < columns; c++)
_board.Add(new MatrixElement(r, c){Color = "Green"});
Board.ItemsSource = _board;
}
private void CellClick(object sender, MouseButtonEventArgs e)
{
var border = (Border)sender;
// each point has unique {X;Y} coordinates
var point = (MatrixElement)border.Tag;
// changing color in item view model
// view is notified by binding
point.Color = "#00BFFF";
}
ItemTemplate was modified a bit
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="{Binding Path=Color}"
BorderBrush="Black"
BorderThickness="1"
MouseDown="CellClick"
Margin="2"
Tag="{Binding}">
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
I'm trying to add several seperate textblocks into a grid dynamically so that adding an element to grid will populate the next open cell.
1 2
3 4
5 6
7 8
...
and so on
When any element is removed, every element following should be shifted to fill in any empty cells so that if 2, 5, 6 are removed (one at a time) it will look like this:
1 3
4 7
8 ...
My XAML and Code are as follows:
<StackPanel x:Name="NumbersStackPanel">
<TextBlock Text="Numbers: "/>
<Grid x:Name="NumbersGrid">
<TextBox x:Name="SearchNumbers"/>
</Grid>
</StackPanel>
CS:
TextBlock newTextBlock = new TextBlock();
newTextBlock.Visibility = Windows.UI.Xaml.Visibility.Visible;
newTextBlock.Foreground = new SolidColorBrush(Colors.Black);
newTextBlock.FontWeight = Windows.UI.Text.FontWeights.SemiBold;
newTextBlock.FontFamily = new Windows.UI.Xaml.Media.FontFamily("Segoe UI Semilight");
newTextBlock.Margin = new Thickness(0, 5, 4, 0);
newTextBlock.TextWrapping = TextWrapping.WrapWholeWords;
newTextBlock.FontSize = 18;
newTextBlock.Text = NumbersModelObj.Number + "; ";
newTextBlock.Tag = NumbersModelObj.NumberId;
textArray.Add(newTextBlock);
NumbersGrid.Children.Insert(NumbersCount, newTextBlock);
NumbersCount ++;
I've tried nested for loops given the value of elements (NumbersCount) but have not been successful in adding more than 2 elements to different cells into different cells in the grid
Are there any relatively simple/clean solutions for achieving this?
I've since found exactly what I was looking for here:
http://windowsapptutorials.com/windows-phone/ui/wrap-grid-with-variable-sized-items/
Hopefully this helps someone down the line.
What you're looking for is a called a UniformGrid. Unfortunately, it was not ported from desktop WPF to Windows Phone WPF.
However, I did some Googling and found this:
UniformGrid.cs
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace App1 // replace with your namespace, of course
{
public class UniformGrid : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
var itemWidth = availableSize.Width / Columns;
foreach (FrameworkElement child in Children)
{
child.Measure(new Size(120, 120));
}
return new Size(availableSize.Width, availableSize.Width);
}
protected override Size ArrangeOverride(Size finalSize)
{
Size cellSize = new Size(finalSize.Width / Columns, finalSize.Width / Columns);
int row = 0, col = 0;
foreach (UIElement child in Children)
{
child.Arrange(new Rect(new Point(cellSize.Width * col, cellSize.Height * row), cellSize));
if (++col == Columns)
{
row++;
col = 0;
}
}
return finalSize;
}
public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public int Rows
{
get { return (int)GetValue(RowsProperty); }
set { SetValue(RowsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(int), typeof(UniformGrid), new PropertyMetadata(1, OnColumnsChanged));
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register("Rows", typeof(int), typeof(UniformGrid), new PropertyMetadata(1, OnRowsChanged));
static void OnColumnsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
int cols = (int)e.NewValue;
if (cols < 1)
((UniformGrid)obj).Columns = 1;
}
static void OnRowsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
int rows = (int)e.NewValue;
if (rows < 1)
((UniformGrid)obj).Rows = 1;
}
}
}
Resource:
https://social.msdn.microsoft.com/Forums/windowsapps/en-US/3254c8ff-a7ad-4346-b353-457cd6ac7a58/uwpcreating-a-uniformgrid-for-listview?forum=wpdevelop
How to use it:
Import UniformGrid.cs into your project; place it wherever you feel is appropriate.
Update the namespace appropriately (remember this namespace for when you use it in the Xaml).
Here's an example:
MainPage.xaml
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:custom="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel x:Name="NumbersStackPanel" Orientation="Vertical">
<TextBlock Text="Numbers: "/>
<custom:UniformGrid x:Name="NumbersGrid" Columns="2">
<custom:UniformGrid.Resources>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="FontFamily" Value="Segoe UI Semilight" />
<Setter Property="Margin" Value="0, 5, 4, 0" />
<Setter Property="TextWrapping" Value="WrapWholeWords" />
<Setter Property="FontSize" Value="18" />
</Style>
</custom:UniformGrid.Resources>
<TextBox Text="1" />
<TextBox Text="2" />
<TextBox Text="3" />
</custom:UniformGrid>
</StackPanel>
</Grid>
</Page>
Note: I'd recommend doing the formatting of textboxes in XAML, as shown. The style, as implemented, targets all TextBoxes within the UniformGrid.
MainPage.xaml.cs
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace App1
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
AddText("hello");
RemoveText("2");
AddText("Goodbye");
RemoveText("hello");
RemoveText("1");
}
private void AddText(string text)
{
TextBox tb = new TextBox();
tb.Text = text;
NumbersGrid.Children.Add(tb);
}
private void RemoveText(string text)
{
foreach(UIElement child in NumbersGrid.Children)
{
TextBox tb = (TextBox)child;
if (tb.Text.Equals(text))
{
NumbersGrid.Children.Remove(tb);
}
}
}
}
}