Here's a Windows Forms program which draws a two-dimensional grid of squares that are randomly colored black or red:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Forms_Panel_Random_Squares
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Width = 350;
Height = 350;
var panel = new Panel() { Dock = DockStyle.Fill };
Controls.Add(panel);
var random = new Random();
panel.Paint += (sender, e) =>
{
e.Graphics.Clear(Color.Black);
for (int i = 0; i < 30; i++)
for (int j = 0; j < 30; j++)
{
if (random.Next(2) == 1)
e.Graphics.FillRectangle(
new SolidBrush(Color.Red),
i * 10,
j * 10,
10,
10);
}
};
}
}
}
The resulting program looks something like this:
Here's a (naive) translation to WPF using Rectangle objects for each square:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WPF_Canvas_Random_Squares
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Width = 350;
Height = 350;
var canvas = new Canvas();
Content = canvas;
Random random = new Random();
Rectangle[,] rectangles = new Rectangle[30, 30];
for (int i = 0; i < rectangles.GetLength(0); i++)
for (int j = 0; j < rectangles.GetLength(1); j++)
{
rectangles[i, j] =
new Rectangle()
{
Width = 10,
Height = 10,
Fill = random.Next(2) == 0 ? Brushes.Black : Brushes.Red,
RenderTransform = new TranslateTransform(i * 10, j * 10)
};
canvas.Children.Add(rectangles[i, j]);
}
}
}
}
The WPF version seems to be way more memory inefficient due to the fact that each cell in the world has the overhead of a Rectangle object.
Is there a way to write this program in a style that's as efficient as the Forms version? Or is there no way around creating all those Rectangle objects?
Here's a pure WPF solution. FrameworkElement is subclassed. This new subclass (DrawingVisualElement) exposes a DrawingVisual object which can be used to draw.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace DrawingVisualSample
{
public class DrawingVisualElement : FrameworkElement
{
private VisualCollection _children;
public DrawingVisual drawingVisual;
public DrawingVisualElement()
{
_children = new VisualCollection(this);
drawingVisual = new DrawingVisual();
_children.Add(drawingVisual);
}
protected override int VisualChildrenCount
{
get { return _children.Count; }
}
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= _children.Count)
throw new ArgumentOutOfRangeException();
return _children[index];
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Width = 350;
Height = 350;
var stackPanel = new StackPanel();
Content = stackPanel;
var drawingVisualElement = new DrawingVisualElement();
stackPanel.Children.Add(drawingVisualElement);
var drawingContext = drawingVisualElement.drawingVisual.RenderOpen();
var random = new Random();
for (int i = 0; i < 30; i++)
for (int j = 0; j < 30; j++)
drawingContext.DrawRectangle(
random.Next(2) == 0 ? Brushes.Black : Brushes.Red,
(Pen)null,
new Rect(i * 10, j * 10, 10, 10));
drawingContext.Close();
}
}
}
It is possible to mix WPF and Forms. So instead of going the pure Forms route, the Panel can be embedded into the WPF Window via WindowsFormsHost. Here's a WPF program which demonstrates this:
using System;
using System.Windows;
using System.Windows.Forms.Integration;
using System.Drawing;
namespace WindowsFormsHost_Random_Squares
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Width = 350;
Height = 350;
Random random = new Random();
var windowsFormsHost = new WindowsFormsHost();
Content = windowsFormsHost;
var panel = new System.Windows.Forms.Panel()
{ Dock = System.Windows.Forms.DockStyle.Fill };
windowsFormsHost.Child = panel;
panel.Paint += (sender, e) =>
{
e.Graphics.Clear(System.Drawing.Color.Black);
for (int i = 0; i < 30; i++)
for (int j = 0; j < 30; j++)
{
if (random.Next(2) == 1)
e.Graphics.FillRectangle(
new SolidBrush(System.Drawing.Color.Red),
i * 10,
j * 10,
10,
10);
}
};
}
}
}
Related
I started yesterday a new proyect in c# with WPF. My first time with it.
I'm trying to do the tictactoe game with graphical interface so i create the grid and i use bottons to change the state (it's not finish yet).
Here is the declaration of the class:
public partial class juego : Window
{
private juego tab;
public juego( int size)
{
InitializeComponent();
this.tab = CreateDynamicWPFGrid(size);
}
Here is the method that created the grid.
public juego CreateDynamicWPFGrid(int size)
{
Grid DynamicGrid = new Grid();
DynamicGrid.Name = "GridTablero";
DynamicGrid.Width = 400;
DynamicGrid.HorizontalAlignment = HorizontalAlignment.Left;
DynamicGrid.VerticalAlignment = VerticalAlignment.Top;
DynamicGrid.ShowGridLines = true;
DynamicGrid.Background = new SolidColorBrush(Colors.LightSteelBlue);
for (int i = 0; i < size; i++)
{
ColumnDefinition gridCol1 = new ColumnDefinition();
DynamicGrid.ColumnDefinitions.Add(gridCol1);
RowDefinition gridRow1 = new RowDefinition();
gridRow1.Height = new GridLength(45);
DynamicGrid.RowDefinitions.Add(gridRow1);
}
for (int fila = 0; fila < DynamicGrid.RowDefinitions.Count; fila++)
{
for (int columna = 0; columna < DynamicGrid.RowDefinitions.Count; columna++)
{
System.Windows.Controls.Button newBtn = new Button();
newBtn.Content = fila.ToString() + "_" + columna.ToString();
newBtn.Name = "Button" + fila.ToString() + "_" + columna.ToString();
newBtn.SetValue(Grid.ColumnProperty, columna);
newBtn.SetValue(Grid.RowProperty, fila);
newBtn.Click += new RoutedEventHandler(button_Click);
DynamicGrid.Children.Add(newBtn);
}
}
tablero.Content = DynamicGrid;
return tablero;
}
So the thing is that i want to iterate over the grid and then, count the buttons which content means if they are X, O or white.
I tried to use in my private method something like tab.Content but i really don't have any idea.
Anyways, i would like to know if this it is even possible.
After all, i come up with a solution by myselft.
So, i changed some lines in the class.
public partial class juego : Window
{
private ArrayList jugadores;
public juego(ArrayList jugadores, int size)
{
InitializeComponent();
tablero.Content = CreateDynamicWPFGrid(size);
}
Then i changed the return from the CreateDynamicWPFGrid:
return dynamicGrid;
And then, one method that i call when i click on buttons.
Grid boardValidar = tablero.Content as Grid;
Button[,] botones = new Button[boardValidar.ColumnDefinitions.Count, boardValidar.ColumnDefinitions.Count];
var buttons = boardValidar.Children.Cast<Button>();
for (int i = 0; i < boardValidar.ColumnDefinitions.Count; i++)
{
for (int j = 0; j < boardValidar.RowDefinitions.Count; j++)
{
botones[i, j] = buttons.Where(x => Grid.GetRow(x) == j && Grid.GetColumn(x) == i).FirstOrDefault();
}
}
So, i cast the grid and then, i do the same with all the buttons.
After this i use the linq to put in the array and that's all.
I know my code it's no the best but i got what i wanted.
I have a class project to create a tic tac toe simulations (game is not played by people) where O's and X's automatically generate when the New Game button is clicked. I am having trouble with the code to get the labels to show the output.
Using a 2D array type INT to simulate the game board, it should store a 0 or 1 in each of the 9 elements and produce a O or X. There also needs to be a label to display if X or O wins.
Here is my code so far ( I know there isn't much, I'm completely lost):
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void StartButton_Click(object sender, EventArgs e)
{
Random rand = new Random();
const int ROWS = 3;
const int COLS = 3;
int[,] gameBoard = new int[ROWS, COLS];
for (int row = 0; row < ROWS; row++)
{
for (int col = 0; col < COLS; col++)
{ gameBoard[row, col]= rand.Next(2); }
}
}
private void ExitButton_Click(object sender, EventArgs e)
{
this.Close();
}
}
}
Win Forms labels get populated unless the current event process finishes completely. If you want to update the labels with X and O while you may use the control property InvokeRequired (boolean) and after assigning the value to label call label.Refresh() function. I will suggest fork a thread on hitting start button and do the for loop -> random.next() inside the thread. Try with these changes. All the Best!!
Try following :
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication4
{
public partial class Form1 : Form
{
const int ROWS = 3;
const int COLS = 3;
const int WIDTH = 100;
const int HEIGHT = 100;
const int SPACE = 20;
static TextBox[,] gameBoard;
public Form1()
{
InitializeComponent();
gameBoard = new TextBox[ROWS, COLS];
for (int row = 0; row < ROWS; row++)
{
for (int col = 0; col < COLS; col++)
{
TextBox newTextBox = new TextBox();
newTextBox.Multiline = true;
this.Controls.Add(newTextBox);
newTextBox.Height = HEIGHT;
newTextBox.Width = WIDTH;
newTextBox.Top = SPACE + (row * (HEIGHT + SPACE));
newTextBox.Left = SPACE + (col * (WIDTH + SPACE));
gameBoard[row, col] = newTextBox;
}
}
}
}
}
I am making a Memory game in WPF and C#. It is going good till now. When I click (turn) 2 cards, I want my code to register that and when the images don't match then I want the back.png image to come back.
Now my code counts how many times there has been clicked but I don't know how to make the cards "turn" again and to make them go away when 2 images match. I have 16 images, 1 and 9 are pairs, 2 and 10 are pairs, and so on.
My plan was to make a method that is called resetCards().
This is my MainWindow.cs:
public partial class MainWindow : Window
{
private MemoryGrid grid;
public MainWindow()
{
InitializeComponent();
}
private void start_Click(object sender, RoutedEventArgs e)
{
grid = new MemoryGrid(GameGrid, 4, 4);
start.Visibility = Visibility.Collapsed;
}
This is my MemoryGrid.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace SpellenScherm
{
public class MemoryGrid
{
private Grid grid;
private int rows, cols;
public MemoryGrid(Grid grid, int rows, int cols)
{
this.grid = grid;
this.rows = rows;
this.cols = cols;
InitializeGrid();
AddImages();
}
private void InitializeGrid()
{
for (int i = 0; i < rows; i++)
{
grid.RowDefinitions.Add(new RowDefinition());
}
for (int i = 0; i < cols; i++)
{
grid.ColumnDefinitions.Add(new ColumnDefinition());
}
}
private void AddImages()
{
List<ImageSource> images = GetImagesList();
for (int row = 0; row < rows; row++)
{
for (int col = 0; col < cols; col++)
{
Image back = new Image();
back.Source = new BitmapImage(new Uri("/images/back.png", UriKind.Relative));
back.MouseDown += new System.Windows.Input.MouseButtonEventHandler(CardClick);
back.Tag = images.First();
images.RemoveAt(0);
Grid.SetColumn(back, col);
Grid.SetRow(back, row);
grid.Children.Add(back);
}
}
}
static int numberOfClicks = 0;
private void resetCards()
{
}
private void CardClick(object sender, MouseButtonEventArgs e)
{
if (numberOfClicks < 2)
{
Image card = (Image)sender;
ImageSource front = (ImageSource)card.Tag;
card.Source = front;
numberOfClicks++;
}
if (numberOfClicks == 2)
{
resetCards();
numberOfClicks = numberOfClicks -2;
}
}
public List<ImageSource> GetImagesList()
{
List<ImageSource> images = new List<ImageSource>();
List<string> random = new List<string>();
for (int i = 0; i < 16; i++)
{
int imageNR = 0;
Random rnd = new Random();
imageNR = rnd.Next(1, 17);
if (random.Contains(Convert.ToString(imageNR)))
{
i--;
}
else
{
random.Add(Convert.ToString(imageNR));
ImageSource source = new BitmapImage(new Uri("images/" + imageNR + ".png", UriKind.Relative));
images.Add(source);
}
}
return images;
}
}
}
You can try this approach - keep two fields in your MemoryGrid class one for each of the images which show their front faces. (Let's call them Image1 and Image2). Then you can keep a track of which cards are flipped in the whole grid and pass them as arguments to your resetCards method as follows:
private void CardClick(object sender, MouseButtonEventArgs e)
{
if (numberOfClicks < 2)
{
Image card = (Image)sender;
ImageSource front = (ImageSource)card.Tag;
card.Source = front;
if(this.Image1 == null){
Image1 = card;
}
else if(this.Image2 == null){
Image2 = card;
}
numberOfClicks++;
}
if (numberOfClicks == 2)
{
resetCards(Image1, Image2);
numberOfClicks = numberOfClicks -2;
}
}
I'm trying to understand how to speed up some graphical controls updates. When there are large numbers of controls on a screen, the updates happen slow as shown in the example below. How can I speed this up so that it updates all the controls at the same time with a 50ms update rate specified in the timer?
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Timer = System.Windows.Forms.Timer;
namespace FormTestApplication
{
public partial class Form1 : Form
{
private List<Label> labels = new List<Label>();
Timer t = new Timer();
private Boolean flag = false;
public Form1()
{
InitializeComponent();
for (int j = 0; j < 20; j++)
{
for (int i = 0; i < 20; i++)
{
Label l = new Label();
l.Top = j * 30;
l.Left = i * 25 + 20;
l.BackColor = Color.Red;
l.Width = 20;
labels.Add(l);
}
}
foreach (var label in labels)
{
this.Controls.Add(label);
}
t.Interval = 50;
t.Tick += t_Tick;
t.Start();
}
void t_Tick(object sender, EventArgs e)
{
foreach (var label in labels)
{
if (label.BackColor == Color.Red)
{
label.BackColor = Color.Green;
}
else
{
label.BackColor = Color.Red;
}
}
}
}
}
I would like to create a user control in WPF, which would be able draw an n x m matrix of characters using a mono-space font.
The control will accept a string[] as fast as possible ( 60 fps is the target) and draw it on the screen.
I need performance similar to mplayer ascii playback.
All characters are drawn using the same mono-space font, but may have different colors and background according to certain rules (similar to syntax highlighting in VS).
I have implemented the solution in C# WinForms without any problem and got to 60 FPS, but when I wanted to learn how to do this in WPF I only found several articles and posts describing problems with WPF performance and conflicting information.
So what is the best way do achieve highest performance in this case?
A naive approach I tried is:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Random rand = new Random();
public MainWindow()
{
InitializeComponent();
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(1);
timer.Tick += timer_Tick;
timer.Start();
}
string GenerateRandomString(int length)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++)
{
sb.Append(rand.Next(10));
}
return sb.ToString();
}
void timer_Tick(object sender, EventArgs e)
{
myTextBlock.Inlines.Clear();
for (int i = 0; i < 30; i++)
{
var run = new Run();
run.Text = GenerateRandomString(800);
run.Foreground = new SolidColorBrush(Color.FromArgb((byte)rand.Next(256),(byte)rand.Next(256),(byte)rand.Next(256),(byte)rand.Next(256)));
run.Background = new SolidColorBrush(Color.FromArgb((byte)rand.Next(256),(byte)rand.Next(256),(byte)rand.Next(256),(byte)rand.Next(256)));
myTextBlock.Inlines.Add(run);
}
}
}
The question is: Can you do better than that in WPF?
P.S.
Yes, I could use DirectX directly, but this question is about WPF and not DX.
Probably the fastest way to draw text in WPF is to use GlyphRun.
Here is a sample code:
MainWindow.xaml
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<Image>
<Image.Source>
<DrawingImage x:Name="drawingImage"/>
</Image.Source>
</Image>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
namespace WpfApplication
{
public partial class MainWindow : Window
{
Random rand = new Random();
Stopwatch stopwatch;
long frameCounter = 0;
GlyphTypeface glyphTypeface;
double renderingEmSize, advanceWidth, advanceHeight;
Point baselineOrigin;
public MainWindow()
{
InitializeComponent();
new Typeface("Consolas").TryGetGlyphTypeface(out this.glyphTypeface);
this.renderingEmSize = 10;
this.advanceWidth = this.glyphTypeface.AdvanceWidths[0] * this.renderingEmSize;
this.advanceHeight = this.glyphTypeface.Height * this.renderingEmSize;
this.baselineOrigin = new Point(0, this.glyphTypeface.Baseline * this.renderingEmSize);
CompositionTarget.Rendering += CompositionTarget_Rendering;
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(1000);
timer.Tick += timer_Tick;
timer.Start();
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
if (this.stopwatch == null)
this.stopwatch = Stopwatch.StartNew();
++this.frameCounter;
this.drawingImage.Drawing = this.Render();
}
string GenerateRandomString(int length)
{
var chars = new char[length];
for (int i = 0; i < chars.Length; ++i)
chars[i] = (char)rand.Next('A', 'Z' + 1);
return new string(chars);
}
void timer_Tick(object sender, EventArgs e)
{
var seconds = this.stopwatch.Elapsed.TotalSeconds;
Trace.WriteLine((long)(this.frameCounter / seconds));
if (seconds > 10)
{
this.stopwatch.Restart();
this.frameCounter = 0;
}
}
private Drawing Render()
{
var lines = new string[30];
for (int i = 0; i < lines.Length; ++i)
lines[i] = GenerateRandomString(100);
var drawing = new DrawingGroup();
using (var drawingContext = drawing.Open())
{
// TODO: draw rectangles which represent background.
// TODO: group of glyphs which has the same color should be drawn together.
// Following code draws all glyphs in Red color.
var glyphRun = ConvertTextLinesToGlyphRun(this.glyphTypeface, this.renderingEmSize, this.advanceWidth, this.advanceHeight, this.baselineOrigin, lines);
drawingContext.DrawGlyphRun(Brushes.Red, glyphRun);
}
return drawing;
}
static GlyphRun ConvertTextLinesToGlyphRun(GlyphTypeface glyphTypeface, double renderingEmSize, double advanceWidth, double advanceHeight, Point baselineOrigin, string[] lines)
{
var glyphIndices = new List<ushort>();
var advanceWidths = new List<double>();
var glyphOffsets = new List<Point>();
var y = baselineOrigin.Y;
for (int i = 0; i < lines.Length; ++i)
{
var line = lines[i];
var x = baselineOrigin.X;
for (int j = 0; j < line.Length; ++j)
{
var glyphIndex = glyphTypeface.CharacterToGlyphMap[line[j]];
glyphIndices.Add(glyphIndex);
advanceWidths.Add(0);
glyphOffsets.Add(new Point(x, y));
x += advanceWidth;
}
y += advanceHeight;
}
return new GlyphRun(
glyphTypeface,
0,
false,
renderingEmSize,
glyphIndices,
baselineOrigin,
advanceWidths,
glyphOffsets,
null,
null,
null,
null,
null);
}
}
}
This answer seems a bit flawed.
Possible cases:
The advance widths are different for each character
There is no effort to align the baselines, since a line may consist
of several glyph runs
Since the absolute y position is buried in the run it can't be easily moved when a line is inserted