I'm currently porting a perl-tk-application to c# wpf. The application supplies a graphical interface with different timelines for different systems. The timelines consist of rectangles - each one representing a special event - that can be clicked for obtaining deeper information about the event. The rectangles have different sizes depending on the length of the event - so their sizes (width) are different and not predictable.
All I need now is the possibility to bind an event to each of the rectangles that - at least - lets me track back which rectangle was clicked. In Perl it was as easy as this:
$rectangle = $Canvas->create_rectangle( $x1, $y1 ,$x2 ,oy2 , -outline => "red", -fill => "red");
$Canvas->bind($rectangle, "<1>", sub {DoAction[$number]});
That means you can just put the event-binding after the element that needs to be clickable.
I've wasted the whole weekend in searching for a solution to do this in c# wpf... Important to know - I'm an absolut newbie in c#.
My code so far: I generated 10 rectangles via array. I want to pass the number of the rectangle-array to the ClickEvent. In the following example code the ClickEvent always prints out the highest index. I assume, there exists only one Event and I would need to generate a array of events...? What's the solution for this? In short words: Which rectangle (number) was clicked?
private void ClickEvent (object sender, EventArgs e, int i) {
var time = DateTime.Now;
string name = ((Shape)sender).Name;
Console.WriteLine("Rectangle click at " + time + " from " + name + " Rect. Nr." + i);
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
int NumObjects = 10;
Rectangle[] RectangleArray = new Rectangle[NumObjects];
for (int i = 0; i < NumObjects - 1; i++) {
RectangleArray[i] = new Rectangle();
RectangleArray[i].Width = 50;
RectangleArray[i].Height = 50;
RectangleArray[i].Fill = Brushes.Red;
Canvas.SetTop(RectangleArray[i], i * 50);
Canvas.SetLeft(RectangleArray[i], i * 50);
RectangleArray[i].MouseLeftButtonDown += (sender2, e2) => ClickEvent(sender2, e2, i);
Canvas1.Children.Add(RectangleArray[i]);
}
}
XAML:
<Window x:Class="WpfRectangleEvent.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfRectangleEvent"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded">
<Grid>
<Canvas HorizontalAlignment="Left" Height="666" Margin="-17,-20,0,0"
VerticalAlignment="Top" Width="517"
Name="Canvas1" Grid.ColumnSpan="2">
</Canvas>
</Grid>
In the event handler method the sender object is the Rectangle which invokes the event.
So if you store the rectangles, you can find out the index of that rectangle.
OK! Got it, this works:
private void ClickEvent(object sender, EventArgs e, int i) {
var time = DateTime.Now;
Console.WriteLine("Rectangle click at " + time + " from Rect. Nr." + i);
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
int NumObjects = 10;
Rectangle[] RectangleArray = new Rectangle[NumObjects];
for (int i = 0; i < NumObjects - 1; i++) {
int index = i;
RectangleArray[i] = new Rectangle();
RectangleArray[i].Width = 50;
RectangleArray[i].Height = 50;
RectangleArray[i].Fill = Brushes.Red;
Canvas.SetTop(RectangleArray[i], i * 50);
Canvas.SetLeft(RectangleArray[i], i * 50);
RectangleArray[i].MouseLeftButtonDown += (sender2, e2) => ClickEvent(sender2, e2, index);
Canvas1.Children.Add(RectangleArray[i]);
}
}
Related
I have the following code. On form load, I want to create multiples labels, the first should be on position (20,0), the second on the (40,0) and till the last label. But the program just shows the first label, I mean label 0, and that's all.
How to fix this?
private void Form1_Load(object sender, EventArgs e)
{
Label[] nmr = new Label[10];
for(int i=0; i<10; i++)
{
nmr[i] = new Label();
nmr[i].Text = "label " + i;
nmr[i].Left += 20;
this.Controls.Add(nmr[i]);
}
}
nmr[i].Left = 20 * (i+1);
will calculate the distance you want. Yet, you will only see one label because the first label is too long. So you have to adjust its size:
nmr[i].Size = new Size(40, 15);
Then you will see that 20 pixels is way too small as a distance; the labels will overlap
private void Form3_Load(object sender, EventArgs e)
{
Label[] nmr = new Label[10];
for (int i = 0; i < 10; i++)
{
nmr[i] = new Label();
nmr[i].Text = "label " + i;
nmr[i].Location = new Point(0, 25 * i);
this.Controls.Add(nmr[i]);
}
this.Height = this.Height + (25 * nmr.Count());
}
you also need to resize your form as well,
this code will help you,
In your loop, replace
nmr[i].Left +=20;
with
nmr[i].Left = 20 * (i + 1);
You should increase the Left value by 20 for each iteration. I also don't see why you are populating an array since you just add the Labels to the Controls collection. Try this:
private void Form1_Load(object sender, EventArgs e)
{
for (int i = 1; i < 11; i++)
{
var label = new Label();
label.Text = "label " + i;
label.Left += 20 * i;
this.Controls.Add(label);
}
}
Actually, you don't edit the right property. The right one would be control.Location which is a Point with the properties x and y.
To add them with 20 for every loop, you actually need to go like (20 * (i+1))
Example code that worked:
private void Form1_Load(object sender, EventArgs e)
{
Label[] nmr = new Label[10];
for (int i = 0; i < 10; i++)
{
nmr[i] = new Label();
nmr[i].Text = "label " + i;
nmr[i].Location = new Point(0, (20 * (i+1)));
this.Controls.Add(nmr[i]);
}
}
EDIT: Work on that 20 pt. Seems like the labels won't show right. Maybe try 30pt?
Try using Linq in order do generate Labels:
using System.Linq;
...
private void Form1_Load(object sender, EventArgs e) {
Label[] nmr = Enumerable
.Range(0, 10)
.Select(i => new Label() {
Text = $"label {i}",
Left = 20 + i * 20, // <- please, notice Left computation
Parent = this, })
.ToArray();
}
I'm trying to place an object on a random location depending on the window size.
LayoutRoot is the name of the grid it's placed in.
//Give Dot a random position
int left = random.Next(LayoutRoot.MinWidth, LayoutRoot.MaxWidth);
int top = random.Next(-900, 900);
Dot.Margin = new Thickness(left, top, 0, 0);
Error on LayoutRoot.MinWidth & MaxWidth: Cannot convert double to int
tried
//Give Dot a random position
double left = random.NextDouble(LayoutRoot.MinWidth, LayoutRoot.MaxWidth);
double top = random.Next(-900, 900);
Dot.Margin = new Thickness(left, top, 0, 0);
Error on NextDouble: Method NextDouble takes 2 arguments
Ok going to up this one up a bit. Modified your code. This code doesn't assume any predefined height or width, it gets that based on the current size of the LayoutRoot. It also offsets the Dot size so it doesn't fall off the screen.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="LayoutRoot">
<Ellipse Width="2" Height="2" Fill="Black" x:Name="Dot"></Ellipse>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow
{
private static Random random = new Random();
public MainWindow()
{
InitializeComponent();
// Don't move dot until the window is loaded, not necessary but generally you don't want to hold up the window from displaying.
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
// The farthest left the dot can be
double minLeft = 0;
// The farthest right the dot can be without it going off the screen
double maxLeft = LayoutRoot.ActualWidth - Dot.Width;
// The farthest up the dot can be
double minTop = 0;
// The farthest down the dot can be without it going off the screen
double maxTop = LayoutRoot.ActualHeight - Dot.Height;
double left = RandomBetween(minLeft, maxLeft);
double top = RandomBetween(minTop, maxTop);
Dot.Margin = new Thickness(left, top, 0, 0);
}
private double RandomBetween(double min, double max)
{
return random.NextDouble() * (max - min) + min;
}
}
}
this might work.
double mWidth = LayoutRoot.MinWidth;
double mxWidth = LayoutRoot.MaxWidth;
double left = random.NextDouble(mWidth, mxWidth);
double top = random.Next(-900, 900);
Dot.Margin = new Thickness(left, top, 0, 0);
}
So after looking at the answer given by Kelly I had a similar issue where the object would not display after loading. My goal was to have the welcome screen of an application randomly move every 10 seconds. So following his design there was an issue where because the grid did not have an actual length due to the lack of column/row definitions we have to force the app to recalculate size at every attempt to randomize.
Here is the XAML I used:
<Grid x:Name="LayoutRoot">
<TextBlock x:Name="WelcomeTextBlock"
Height="200"
Style="{StaticResource WelcomeTextBlock}">
Welcome!
</TextBlock>
<Ellipse Width="2" Height="2" x:Name="Dot"/>
</Grid>
And more importantly the actual code-behind:
public partial class WelcomePage : Page
{
private static Random Rnd = new Random();
private static DispatcherTimer _timer;
public WelcomePage()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnClosing;
}
private void OnLoaded(object sender, RoutedEventArgs args)
{
this.LayoutRoot.Margin = GetNewMargin();
_timer = new DispatcherTimer {Interval = TimeSpan.FromSeconds(5)};
_timer.Tick += MoveWelcome;
_timer.Start();
}
private void MoveWelcome(object sender, EventArgs e)
{
this.LayoutRoot.Margin = GetNewMargin();
}
private Thickness GetNewMargin()
{
this.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
this.Arrange(new Rect(0, 0, this.DesiredSize.Width, this.DesiredSize.Height));
var maxLeft = this.LayoutRoot.ActualWidth - Dot.Width;
var maxTop = LayoutRoot.ActualHeight - Dot.Height;
var left = RandomBetween(0, maxLeft);
var top = RandomBetween(0, maxTop);
return new Thickness(left, top, 0 ,0);
}
private static void OnClosing(object sender, RoutedEventArgs args)
{
_timer.Stop();
}
private static double RandomBetween(double min, double max) => Rnd.NextDouble() * (max - min) + max;
Besides the timer functionality the major change here is that when the margin thickness is being calculated we force the window, or in my case page to update the measurements of the screen and arrange the items correctly.
How can I get PropertyGrid's TextBox from specified field?
I need this TextBox to set Pointer to the end of text.
var num = txtBox.GetCharIndexFromPosition(Cursor.Position);
txtBox.SelectionStart = num + 1;
txtBox.SelectionLength = 0;
So how can I get this TextBox from PropertyGrid?
Also, property in PropertyGrid is read-only.
If what you want is the cursor to be located right after the last character written in the textbox, you can rely on the following code (triggered by the TextChanged Event of the TextBox):
private void txtBox_TextChanged(object sender, EventArgs e)
{
int newX = txtBox.Location.X + TextRenderer.MeasureText(txtBox.Text, txtBox.Font).Width;
int newY = txtBox.Bottom - txtBox.Height / 2;
if (newX > txtBox.Location.X + txtBox.Width)
{
newX = txtBox.Location.X + txtBox.Width;
}
Cursor.Position = this.PointToScreen(new Point(newX, newY));
}
Bear in mind that its Y position is always in the middle.
----- UPDATE AFTER THE KINGKING COMMENT
As far as the code in the question was referred to the TextBox, I focused my answer on the TextBox. Nonetheless, KingKing is right and the PropertyGrid has to be brought into consideration. Below these lines I adapted the code you can find in MSDN for PropertyGrid:
private void Form1_Load(object sender, EventArgs e)
{
PropertyGrid propertyGrid1 = new PropertyGrid();
propertyGrid1.CommandsVisibleIfAvailable = true;
propertyGrid1.Location = new Point(10, 20);
propertyGrid1.Size = new System.Drawing.Size(400, 300);
propertyGrid1.TabIndex = 1;
propertyGrid1.Text = "Property Grid";
this.Controls.Add(propertyGrid1);
propertyGrid1.SelectedObject = txtBox;
}
After txtBox is added to propertyGrid1, its position is updated and thus the original code can be used without any problem.
In summary, the idea is not looking for the TextBox inside the PropertyGrid, but accessing directly the TextBox control (which is added at runtime to the PropertyGrid).
My app creates a list of 20 Buttons that are placed on mainpage.xaml:
private List<Button> CreateList()
{
for (int i = 0; i <= 19; i++)
{
string name = string.Format("button{0}", i+1);
Button buttt = new Button();
buttt.Name = name;
buttt.Content = i + 1;
buttt.Height = 72;
buttt.HorizontalAlignment = HorizontalAlignment.Left;
buttt.VerticalAlignment = VerticalAlignment.Top;
buttt.Width = 88;
buttt.Click += new RoutedEventHandler(this.button_Click);
GameGrid.Children.Add(buttt);
myList.Insert(i, buttt);
}
Now if I try to shuffle this list, it seems to lose its connection to the actual buttons on the page.
private void Shuffle(List<Button> list)
{
//list[1].Content = "DING!";
Random rand = new Random();
int n = list.Count;
while (n > 1)
{
n--;
int k = rand.Next(n + 1);
Button value = list[k];
list[k] = list[n];
list[n] = value;
}
}
Note that if i un-comment //list[1].Content = "DING!"; and comment out the rest of this method the button's content IS changed on the screen. So I'd assume the link is broken during the shuffle.
So my problem is that when I run this code, the buttons are displayed but are still in order from 1 to 20, instead of being shuffled randomly like I've intended.
Thanks for the help!
Edit: Here is the full code with Chris's suggestions:
private List<Button> CreateList(List<Marginz> myMargin)
{
for (int i = 0; i <= 19; i++)
{
string name = string.Format("button{0}", i+1);
Button buttt = new Button();
buttt.Name = name;
buttt.Content = i + 1;
buttt.Height = 72;
buttt.HorizontalAlignment = HorizontalAlignment.Left;
buttt.VerticalAlignment = VerticalAlignment.Top;
buttt.Width = 88;
buttt.Click += new RoutedEventHandler(this.button_Click);
Thickness myThickness = new Thickness();
myThickness.Left = myMargin[i].left;
myThickness.Top = myMargin[i].top;
myThickness.Right = myMargin[i].right;
myThickness.Bottom = myMargin[1].bottom;
buttt.Margin = myThickness;
//GameGrid.Children.Add(buttt);
myList.Insert(i, buttt);
}
return myList;
}
And here is where it's called:
private void EasyButton_Click(object sender, RoutedEventArgs e)
{
DifficultyCanvas.Visibility = System.Windows.Visibility.Collapsed;
ReadyCanvas.Visibility = System.Windows.Visibility.Visible;
//set difficulty attributes
difficulty = "Easy";
var myMarg = CreateMarginList(marg);
var buttons = CreateList(myMarg);
Shuffle(buttons);
foreach (var button in buttons)
{
GameGrid.Children.Add(button);
}
}
Edit for more explanation:
About the Margins. I've created a class called Marginz:
public class Marginz
{
public Marginz()
{
//Constructor
}
public int left { get; set; }
public int top { get; set; }
public int right { get; set; }
public int bottom { get; set; }
}
"marg" is a List of this type:
List<Marginz> marg = new List<Marginz>(20);
And CreateMarginList() does this:
public List<Marginz> CreateMarginList(List<Marginz> myMarg)
{
Marginz one = new Marginz();
one.left = 28;
one.top = 186;
one.right = 0;
one.bottom = 0;
myMarg.Insert(0, one);
Marginz two = new Marginz();
two.left = 133;
two.top = 186;
two.right = 0;
two.bottom = 0;
myMarg.Insert(1, two);
etc all the way to twenty. Then return myMarg;
So every Button has a unique margin placing it in the Grid.
Shuffling the myList collection won't change the order they appear on the page. That is determined by the order you add them to the GameGrid in your CreateList method. What you can do instead is create them all, shuffle the list, then add them to the Children listing.
So remove the GameGrid.Children.Add call in CreateList (note, I kinda tweaked the code there, I'm assuming you weren't posting full code)
private List<Button> CreateList()
{
var myList = new List<Button>();
for (int i = 0; i <= 19; i++)
{
string name = string.Format("button{0}", i+1);
Button buttt = new Button();
buttt.Name = name;
buttt.Content = i + 1;
buttt.Height = 72;
buttt.HorizontalAlignment = HorizontalAlignment.Left;
buttt.VerticalAlignment = VerticalAlignment.Top;
buttt.Width = 88;
buttt.Click += new RoutedEventHandler(this.button_Click);
myList.Add(buttt);
}
return myList;
}
Perform your shuffle, then add them:
var buttons = CreateList();
Shuffle(buttons);
foreach(var button in buttons)
{
GameGrid.Children.Add(button);
}
EDIT: From your full code that you posted, the problem is that because all of the buttons are in a Grid control, their positioning is dictated by which row/column their in and their Margin (which controls their positioning within that cell). If you do not explicitly define their row/column, which you do not, then they're assumed to be in the first row/column. In this case, their margins, which are not shuffled, dictate their positioning.
I think in this case, either build the Grid with cells, or probably most easily: simply shuffle the myMargin list instead before creating the list! The buttons will be added in order, but they'll be given random positions.
var myMargin = CreateMargins(); //wherever that's done
Shuffle(myMargin); //you'll have to change the signature to work against List<Thickness> instead
var buttons = CreateList(myMargin); //add back the GameGrid.Children.Add call
//notice, no longer a call to shuffle the buttons
Might not be the best solution, but I think this will give you the same effect you were going for.
Your logic is flawed. You're putting the buttons in a grid, and positioning them with the margin. Since you're using the margin to position them, their position won't change no matter in which order you add them to the grid.
A few ways to do what you're trying to do:
Apply the margin after the button list has been shuffled
Use a Stackpanel instead of a grid (and remove the margin)
Use the grid as it's intended to be: create some rows, and assign each button to a row
<Grid>
<Grid.RowDefinitions>
<Grid.RowDefinition />
<Grid.RowDefinition />
<Grid.RowDefinition />
etc...
</Grid.RowDefinitions>
<Grid>
Then in the code behind, set the row of each button:
Grid.SetRow(myButton, 1);
You could just shuffle the index of the buttons in the UIElementCollection, But you may have to switch grid types as setting all the Margins explicity in your code will not allow it to shuffle.
Are you able to Use StackPanel, WrapPanel or UniformGrid to layout your controls ??
Example based on UniformGrid (same will work for all grids unless you have set Margins on the controls).
I know its not an answer to you current question but it may point you in the right direction.
private void Shuffle(UIElementCollection list)
{
Random rand = new Random();
int n = list.Count;
while (n > 1)
{
n--;
int k = rand.Next(n + 1);
Button value = list.OfType<Button>().ElementAt(n);
list.Remove(value);
list.Insert(k, value);
}
}
Example:
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="199" Width="206" Name="UI">
<Grid>
<UniformGrid Name="GameGrid" Margin="0,48,0,0" Height="112" VerticalAlignment="Top">
<Button Content="Button 1" />
<Button Content="Button 2" />
<Button Content="Button 3" />
<Button Content="Button 4" />
<Button Content="Button 5" />
</UniformGrid>
<Button Content="Shuffle" Height="23" HorizontalAlignment="Left" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</Window>
Code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
Shuffle(GameGrid.Children);
}
private void Shuffle(UIElementCollection list)
{
Random rand = new Random();
int n = list.Count;
while (n > 1)
{
n--;
int k = rand.Next(n + 1);
Button value = list.OfType<Button>().ElementAt(n);
list.Remove(value);
list.Insert(k, value);
}
}
}
I am relatively new to C# and I'm trying to create a window with a grid of a dynamic amount of same-sized squares. The squares are then to have their color changed by a process.
I'm currently struggling to generate the grid of squares. Whenever I run the application it seems to be going crazy on resources and I'm not sure why.
The code I am using follows:
private void Window_Loaded(object sender, RoutedEventArgs e) {
//create a blue brush
SolidColorBrush vBrush = new SolidColorBrush(Colors.Blue);
//set the columns and rows to 100
cellularGrid.Columns = mCellAutomaton.GAME_COLUMNS;
cellularGrid.Rows = mCellAutomaton.GAME_ROWS;
//change the background of the cellular grid to yellow
cellularGrid.Background = Brushes.Yellow;
//create 100*100 blue rectangles to fill the cellular grid
for (int i = 0; i < mCellAutomaton.GAME_COLUMNS; i++) {
for (int j = 0; j < mCellAutomaton.GAME_ROWS; j++) {
Rectangle vRectangle = new Rectangle();
vRectangle.Width = 10;
vRectangle.Height = 10;
vRectangle.Fill = vBrush;
cellularGrid.Children.Add(vRectangle);
}
}
}
Is this even the approach I want to take if I want a modifiable grid of squares?
Thanks for any help,
Jason
this seems to perform pretty fast
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="350" Name="UI">
<Grid Name="Test">
<UniformGrid Name="UniGrid" />
</Grid>
</Window>
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
AddRows(new Size(10, 10));
}
private void AddRows(Size recSize)
{
UniGrid.Columns = (int)(UniGrid.ActualWidth / recSize.Width);
UniGrid.Rows = (int)(UniGrid.ActualHeight / recSize.Height);
for (int i = 0; i < UniGrid.Columns * UniGrid.Rows; i++)
{
UniGrid.Children.Add(new Rectangle { Fill = new SolidColorBrush(Colors.Yellow), Margin = new Thickness(1) });
}
}
}