Draw samurai sudoku grid on WPF - c#

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}"/>

Related

How to create and use matrix of (color) boxes C# WPF

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>

Deriving from VirtualizingStackPanel does not work

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.

Showing canvas children in listbox

I'm new here so please excuse my if I missed to add something needed to answer my question.
So heres my question:
I am trying to add shapes to a canvas while also wanting to show a list of them in a listbox, to make them changeable (size,position, etc.). I am using WPF. Is there a way to do so?
And if it doesnt bother you: Is there maybe a question or website or whatever about how to dynamically draw shapes(circle,ellipse,rect, etc.) with mouse events?
I hope you can help me. Thanks in advance.
Edit:
Given the fact that I have:
public partial class MainWindow : Window
{
public ObservableCollection<string> Baselist = new ObservableCollection<string>();
public ObservableCollection<string> Crystallist = new ObservableCollection<string>();
public ObservableCollection<Shape> Shapelist = new ObservableCollection<Shape>();
public MainWindow()
{
this.ResizeMode = System.Windows.ResizeMode.CanMinimize;
InitializeComponent();
InitializeLists(Baseforms,CrystalGroups);
}
private void InitializeLists(ComboBox Baseforms, ComboBox CrystalGroups)
{
Baseforms.ItemsSource = Baselist;
CrystalGroups.ItemsSource = Crystallist;
Shape Circle = new Ellipse();
Circle.Stroke = System.Windows.Media.Brushes.Black;
Circle.Fill = System.Windows.Media.Brushes.DarkBlue;
Circle.HorizontalAlignment = HorizontalAlignment.Left;
Circle.VerticalAlignment = VerticalAlignment.Center;
Circle.Width = 50;
Circle.Height = 50;
Shapelist.Add(Circle);
}
How can I use an ItemsControl to show the shapes in Shapelist in an canvas while also listing them in a Listbox?
Hope this makes the question less broad.
Please try the next solution:
Updated version (xaml and code behind)
DetailsList list view - presents detailed data based on ShapeDataPresentation class (supports multi-select). Shows data by data template named ShapeDataPresentationDataTemplate.
ShapesPresentor items control - presents shapes on canvas (doesn't support multi-select only one can be selected).
ListView XAML code ("This" is the name of the ListView containing window)
<Window x:Class="ListViewWithCanvasPanel.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:listViewWithCanvasPanel="clr-namespace:ListViewWithCanvasPanel"
Title="MainWindow" Height="350" Width="525" x:Name="This" ResizeMode="CanResize">
<Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2.5*"></ColumnDefinition>
<ColumnDefinition Width="4*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListView x:Name="DetailsList" Panel.ZIndex="999" Grid.Column="0" ItemsSource="{Binding ElementName=This, Path=Shapes}" SelectionMode="Extended" SelectionChanged="Selector_OnSelectionChanged">
<ListView.Resources>
<DataTemplate x:Key="ShapeDataPresentationDataTemplate" DataType="listViewWithCanvasPanel:ShapeDataPresentation">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name, StringFormat={}{0:N0}%}"></TextBlock>
<TextBlock Grid.Column="1">
<Run Text="W:"></Run>
<Run Text="{Binding OriginalRectAroundShape.Width}"></Run>
</TextBlock>
<TextBlock Grid.Column="2">
<Run Text="H:"></Run>
<Run Text="{Binding OriginalRectAroundShape.Height}"></Run>
</TextBlock>
</Grid>
</DataTemplate>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="Shape">
<ContentControl Content="{Binding Tag}" ContentTemplate="{StaticResource ShapeDataPresentationDataTemplate}"></ContentControl>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
<GridSplitter Grid.Column="0" Width="3" Background="Blue" Panel.ZIndex="999"
VerticalAlignment="Stretch" HorizontalAlignment="Right" Margin="0"/>
<ItemsControl Grid.Column="1" x:Name="ShapesPresentor" ItemsSource="{Binding ElementName=This, Path=Shapes}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
MouseDown="UIElement_OnMouseDown">
<!--<ListView.Resources>
<ControlTemplate x:Key="SelectedTemplate" TargetType="ListViewItem">
<ContentControl Content="{Binding }"></ContentControl>
</ControlTemplate>
</ListView.Resources>-->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="White" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!--<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true" />
<Condition Property="Selector.IsSelectionActive" Value="true" />
</MultiTrigger.Conditions>
<Setter Property="Template" Value="{StaticResource SelectedTemplate}" />
</MultiTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>-->
</ItemsControl>
</Grid>
<StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Orientation="Horizontal">
<ComboBox x:Name="Baseforms"></ComboBox>
<ComboBox x:Name="CrystalGroups"></ComboBox>
</StackPanel>
</Grid>
Code behind (updated)
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public static readonly DependencyProperty ShapesProperty = DependencyProperty.Register(
"Shapes", typeof (ObservableCollection<Shape>), typeof (MainWindow),
new PropertyMetadata(default(ObservableCollection<Shape>)));
public ObservableCollection<Shape> Shapes
{
get { return (ObservableCollection<Shape>) GetValue(ShapesProperty); }
set { SetValue(ShapesProperty, value); }
}
public ObservableCollection<string> Baselist = new ObservableCollection<string> {"a", "b", "c"};
public ObservableCollection<string> Crystallist = new ObservableCollection<string>{"aa", "bb", "cc"};
public ObservableCollection<Shape> Shapelist = new ObservableCollection<Shape>();
private SolidColorBrush _originalColorBrush = Brushes.Tomato;
private SolidColorBrush _selectedColorBrush;
private double _diameter;
public MainWindow()
{
_diameter = 50d;
this.ResizeMode = System.Windows.ResizeMode.CanMinimize;
Shapes = new ObservableCollection<Shape>();
InitializeComponent();
InitializeLists(Baseforms, CrystalGroups);
}
private void InitializeLists(ComboBox Baseforms, ComboBox CrystalGroups)
{
Baseforms.ItemsSource = Baselist;
CrystalGroups.ItemsSource = Crystallist;
Shape Circle = new Ellipse();
Circle.Stroke = System.Windows.Media.Brushes.Black;
Circle.Fill = System.Windows.Media.Brushes.DarkBlue;
Circle.HorizontalAlignment = HorizontalAlignment.Left;
Circle.VerticalAlignment = VerticalAlignment.Center;
Circle.Width = 50;
Circle.Height = 50;
Shapelist.Add(Circle);
}
private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
{
var inputElement = sender as IInputElement;
if (inputElement == null) return;
var point = e.GetPosition(inputElement);
Shape shape = new Ellipse
{
Stroke = Brushes.Black,
Fill = _originalColorBrush,
Width = _diameter,
Height = _diameter
};
var byX = point.X - _diameter / 2d;
var byY = point.Y - _diameter / 2d;
var existingShape = Shapes.FirstOrDefault(shapeToCheck =>
{
var data = shapeToCheck.Tag as ShapeDataPresentation;
if (data == null) return false;
var res = data.OriginalRectAroundShape.IntersectsWith(new Rect(point,point));
return res;
});
if (existingShape == null)
{
var shapeDataPresentation = new ShapeDataPresentation { Name = string.Format("Ox:{0}, Oy:{1}", point.X.ToString("##.###"), point.Y.ToString("##.###")), OriginalRectAroundShape = new Rect(new Point(byX, byY), new Size(_diameter, _diameter)) };
shape.Tag = shapeDataPresentation;
shape.ToolTip = new ToolTip{Content = shapeDataPresentation.Name};
var translateTransform = new TranslateTransform(byX, byY);
shape.RenderTransform = translateTransform;
Shapes.Add(shape);
}
else
{
if (DetailsList.SelectedItems.Contains(existingShape) == false)
{
DetailsList.SelectedItems.Clear();
DetailsList.SelectedItems.Add(existingShape);
}
}
}
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var currentSelected = e.AddedItems.OfType<Shape>().ToList();
var prevSelected = e.RemovedItems.OfType<Shape>().ToList();
if (currentSelected.Count > 0)
{
currentSelected.ForEach(shape =>
{
_selectedColorBrush = Brushes.CadetBlue;
shape.Fill = _selectedColorBrush;
});
}
if (prevSelected.Count > 0)
{
prevSelected.ForEach(shape =>
{
shape.Fill = _originalColorBrush;
});
}
}
}
public class ShapeDataPresentation
{
public string Name { get; set; }
public Rect OriginalRectAroundShape { get; set; }
}
How it is looks like
Summary:
Here you can create item by mouse click on canvas, mouse down handled
in code (UIElement_OnMouseDown).
Allowed selection and multi-selection, each time you make selection it will be handled in code (Selector_OnSelectionChanged).
But it is better to use the MVVM based approach to work in wpf.
Regards.

How to Vertically "center" align the multi line text

Sorry if the title a bit misleading, I have difficulty to entitle it. It's also not exactly "center". (see below for further explanations)
Hi, I am going to make a project to write numbered musical scores. But I found an obstacle on leveling the "font". (See below picture for visual)
I need 1 2 3 3 1 2 3 4 4 to be in a straight "line"
I use WrapPanel as the container of my notes (as in the screen).
The main problem is on the pitch dot, which located EITHER above or below the note. Pitch is bound to the note, so I need process it in 1 User Control. But, if so, then I can't control the placement of the note "font" to be in one line.
Below is my note user control code :
XAML
<UserControl x:Class="RevisiConverter.NumericNoteBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Width="{Binding ActualWidth, ElementName=txbNote}"
Height="{Binding ActualHeight, ElementName=mainStack}">
<StackPanel Name="mainStack">
<StackPanel Name="topStack">
</StackPanel>
<Canvas Name="canvas" Width="{Binding ActualWidth, ElementName=txbNote}" Height="{Binding ActualHeight, ElementName=txbNote}"
HorizontalAlignment="Center">
<TextBlock Name="txbNote" Text="1" Foreground="Black" FontSize="15"
Margin="0,0,0,-3" FontFamily="Courier" Canvas.Top="0" Canvas.Left="0"/>
</Canvas>
<StackPanel Name="botStack">
</StackPanel>
</StackPanel>
XAML.CS
public partial class NumericNoteBox : UserControl
{
private Note _child;
private Line _sharp;
public Note Child
{
get { return _child; }
set
{
_child = value;
Update();
}
}
public NumericNoteBox()
{
InitializeComponent();
_sharp = null;
}
public void Update()
{
txbNote.Text = _child.MainNote.ToString();
if (_sharp != null)
{
_sharp.Visibility = Visibility.Hidden;
}
topStack.Children.Clear();
botStack.Children.Clear();
if (_child != null)
{
if (_child.Pitch > 0)
{
for (int i = 0; i < _child.Pitch; i++)
{
topStack.Children.Add(new Ellipse());
(topStack.Children[topStack.Children.Count - 1] as Ellipse).Width = 3;
(topStack.Children[topStack.Children.Count - 1] as Ellipse).Height = 3;
(topStack.Children[topStack.Children.Count - 1] as Ellipse).Fill = Brushes.Black;
if (_child.Accidental != Note.Accidentals.Flat)
{
(topStack.Children[topStack.Children.Count - 1] as Ellipse).Margin = new Thickness(0, 1, 0, 0);
}
else
{
(topStack.Children[topStack.Children.Count - 1] as Ellipse).Margin = new Thickness(8, 1, 0, 0);
}
}
}
else if (_child.Pitch < 0)
{
for (int i = 0; i < Math.Abs(_child.Pitch); i++)
{
botStack.Children.Add(new Ellipse());
(botStack.Children[botStack.Children.Count - 1] as Ellipse).Width = 3;
(botStack.Children[botStack.Children.Count - 1] as Ellipse).Height = 3;
(botStack.Children[botStack.Children.Count - 1] as Ellipse).Fill = Brushes.Black;
if (_child.Accidental != Note.Accidentals.Flat)
{
(botStack.Children[botStack.Children.Count - 1] as Ellipse).Margin = new Thickness(0, 1, 0, 0);
}
else
{
(botStack.Children[botStack.Children.Count - 1] as Ellipse).Margin = new Thickness(8, 1, 0, 0);
}
}
}
if (_child.Accidental == Note.Accidentals.Flat)
{
txbNote.Text = "b" + _child.MainNote.ToString();
}
else if (_child.Accidental == Note.Accidentals.Sharp)
{
if (_sharp == null)
{
_sharp = new Line();
_sharp.X1 = 10;
_sharp.Y1 = 2.5;
_sharp.X2 = -2.5;
_sharp.Y2 = 12.5;
_sharp.StrokeThickness = 1;
_sharp.Stroke = Brushes.Black;
canvas.Children.Add(_sharp);
}
_sharp.Visibility = Visibility.Visible;
}
}
}
}
Note :
I'm not bound to that code, so if any of you have dealt to things like this more efficiently with totally different approach, then it's always welcome.
Sorry for some grammar error, if any, since english is not my first language.
I feel that I have not explained my problem real clear, since I also quite confused on it, so, please clarify anything you need.
Thanks
ADDITIONAL DETAILS :
The dot theoretically doesn't have certain limitation, but in practice, it's usually only 3 maximum (either 3 on top or 3 on bottom - not both)
In my code above, the note user control is divided into 3 grid row, the top one is for stacking the top dot, the mid one is for visualizing the note (numbers) and the bot one is for stacking the bot dot.
Please clarify anything, and I'll add more.
You should do this with an ItemsControl which use an appropriate ItemTemplate for the numeric notes.
First, create a ViewModel that defines a collection of "numeric note" items:
public class NumericNoteItem
{
public int Number { get; set; }
public int Pitch { get; set; }
}
public class ViewModel
{
public ViewModel()
{
NumericNotes = new ObservableCollection<NumericNoteItem>();
}
public ObservableCollection<NumericNoteItem> NumericNotes { get; private set; }
}
In the constructor of your MainWindow, you may set the DataContext to an instance of this ViewModel like this:
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
vm.NumericNotes.Add(new NumericNoteItem { Number = 1, Pitch = 0 });
vm.NumericNotes.Add(new NumericNoteItem { Number = 2, Pitch = 1 });
vm.NumericNotes.Add(new NumericNoteItem { Number = 3, Pitch = -1 });
vm.NumericNotes.Add(new NumericNoteItem { Number = 4, Pitch = 0 });
vm.NumericNotes.Add(new NumericNoteItem { Number = 5, Pitch = 2 });
vm.NumericNotes.Add(new NumericNoteItem { Number = 6, Pitch = -2 });
vm.NumericNotes.Add(new NumericNoteItem { Number = 4, Pitch = 0 });
vm.NumericNotes.Add(new NumericNoteItem { Number = 5, Pitch = 3 });
vm.NumericNotes.Add(new NumericNoteItem { Number = 6, Pitch = -3 });
DataContext = vm;
}
For visualizing the pitch I'd recommend to use a Path object with a Geometry that consists of a number of EllipseGeometries. To achieve this you would implement a binding converter that converts the pitch number into a Geometry, like shown below. It uses the parameter argument of the Convert method to create a Geometry for either positive or negative pitch values.
public class NotePitchConverter : IValueConverter
{
private const double radius = 1.5;
private const double distance = 2 * radius + 1;
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var pitch = (int)value;
var geometry = new GeometryGroup();
if (parameter as string == "Bottom")
{
pitch = -pitch;
}
for (int i = 0; i < pitch; i++)
{
geometry.Children.Add(new EllipseGeometry(
new Point(radius, radius + i * distance), radius, radius));
}
return geometry;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Now, in XAML you would create an instance of this converter in e.g. the MainWindow Resources:
<Window.Resources>
<local:NotePitchConverter x:Key="NotePitchConverter"/>
</Window.Resources>
and write the ItemsControl like shown below. Note that the DataTemplate uses a fixed height for the Path elements that show the top and bottom pitch. If you need to show more than three dots, you'd need to increase their height.
<ItemsControl ItemsSource="{Binding NumericNotes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="2">
<Grid.RowDefinitions>
<RowDefinition Height="12"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="12"/>
</Grid.RowDefinitions>
<Path Grid.Row="0" Fill="Black" HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Data="{Binding Pitch,
Converter={StaticResource NotePitchConverter}}"/>
<TextBlock Grid.Row="1" Text="{Binding Number}"/>
<Path Grid.Row="2" Fill="Black" HorizontalAlignment="Center"
VerticalAlignment="Top"
Data="{Binding Pitch,
Converter={StaticResource NotePitchConverter},
ConverterParameter=Bottom}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

How to improve speed of adding controls to UI

I have the following code which creates WPF controls and then adds them to a window in a fashion I need. It works decently, but when trying to create 256 (x4 - two textblocks, combo box, textbox) controls it takes a while to display the tab. Window loads fine but I have many tabs and when I click on point setup tab it lags a little before displaying the tab. It only lags the first time I click on the tab, every time after the first it responds immediately.
At first I thought it was a rendering issue, but after much other research I am of the impression C#/WPF doesn't do well with creating a bunch of objects on the fly and adding them to forms.
If I drop the number of items to 50 it responds immediately, 100 is a slight lag and 200 (256) is a little more of a lag and too much to be acceptable to users.
Any experiences with issues like this before and advice for how to fix it or other tips/tricks.
Thanks in advance!
Wesley
public static void pointSetup(VirtualizingStackPanel desc, VirtualizingStackPanel map) //Draws point description and point map table in point setup tab
{
StackPanel row;
TextBlock text;
TextBox textBox;
ComboBox comboBox;
Thickness rowSpacing = new Thickness(0, 0, 0, 5);
Thickness textSpacing = new Thickness(0, 3, 5, 3);
List<string> list = new List<string>();
list.Add("xx");
for (byte i = 0; i < Global.currentZonesToMap; i++)
{
list.Add("Zone " + (i + 1));
}
for (short i = 0; i < 256; i++)
{
//desc
row = new StackPanel();
row.Margin = rowSpacing;
row.Orientation = Orientation.Horizontal;
text = new TextBlock();
text.Text = "Point " + (i + 1);
text.Margin = textSpacing;
text.Width = 50;
textBox = new TextBox();
textBox.MaxLength = 28;
textBox.Text = "";
textBox.Width = 270;
row.Children.Add(text);
row.Children.Add(textBox);
desc.Children.Add(row);
//map
row = new StackPanel();
row.Margin = rowSpacing;
row.Orientation = Orientation.Horizontal;
text = new TextBlock();
text.Text = "Point " + (i + 1);
text.Margin = textSpacing;
text.Width = 50;
comboBox = new ComboBox();
comboBox.ItemsSource = list;
comboBox.Width = 270;
row.Children.Add(text);
row.Children.Add(comboBox);
map.Children.Add(row);
}
}
New Code (using DataTemplate and ItemsControl)
public class DevicePoint
{
public string desc { get; set; }
public int zone { get; set; }
public List<string> zones { get; set; }
}
//Initialized all variables and displays UI (constructor)
public Dispatcher()
{
InitializeComponent();
List<string> opts = new List<string>();
opts.Add("xx");
for (byte i = 0; i < Global.currentZonesToMap; i++)
{
opts.Add("Zone " + (i + 1));
}
List<DevicePoint> points = new List<DevicePoint>();
for (short i = 0; i < 256; i++)
points.Add(new DevicePoint() { desc = "Point " + (i + 1), zone = 0, zones = opts });
pointDesc.ItemsSource = points;
pointZoneMap.ItemsSource = points;
... other stuff here ...
}
<StackPanel>
<TextBlock Margin="10" FontWeight="Bold" HorizontalAlignment="Center" Text="Point Descriptions" />
<ScrollViewer Width="360" Margin="30,10,30,10" MaxHeight="405">
<ItemsControl Name="pointDesc" Margin="5">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel VirtualizingStackPanel.IsVirtualizing="True" Margin="0,0,0,5" Orientation="Horizontal">
<TextBlock Margin="0,3,5,3" Width="50" Text="{Binding desc}" />
<TextBox MaxLength="28" Width="270" Text="{Binding desc}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
<StackPanel Grid.Column="1">
<TextBlock Margin="10" FontWeight="Bold" HorizontalAlignment="Center" Text="Point - Zone Map" />
<ScrollViewer Width="360" Margin="30,10,30,10" MaxHeight="405">
<ItemsControl Name="pointZoneMap" Margin="5">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel VirtualizingStackPanel.IsVirtualizing="True" Margin="0,0,0,5" Orientation="Horizontal">
<TextBlock Margin="0,3,5,3" Width="50" Text="{Binding desc}" />
<ComboBox Width="270" ItemsSource="{Binding zones}" SelectedIndex="{Binding zone}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
If your computer have multiple cores, and I am assuming it have, try to perform the for loop in parallel (parallelfor (from .net 4 or above).
You can set the points List size during creation to 256, this will prevent memory allocations during the items adding operation.
Consider use a StringBuilder if Global.currentZonesToMap is large.
Use StringBuilder to build the value for the DevicePoint.desc string property.
Good luck,
M. Moshe

Categories

Resources