Dynamic Picturebox creation into just showed MdiChildForm - c#

hope some of you can help me.
I have one PictureBox Array to show into an MdiChildForm just after it's showed.
But when I run the program, in the "CreatePictureBox" Method, which I execute only after I show the form I get a NullReferenceException. Why?
Here's the code of FormShowing
private void packOpeningToolStripMenuItem_Click(object sender, EventArgs e)
{
ProvaPackOpening ProvaPackOpening = new ProvaPackOpening();
ProvaPackOpening.MdiParent = this;
ProvaPackOpening.Show();
ProvaPackOpening.CreatePictureBox();
}
And that's the code of the array creation
public void CreatePictureBox()
{
Int16 i = 0;
PictureBox[] PicBoxArray = new PictureBox[10];
while (i < PicBoxArray.Count())
{
PicBoxArray[i].BackgroundImageLayout = ImageLayout.Center;
PicBoxArray[i].Location = new Point(0, 0);
PicBoxArray[i].Size = new Size(128, 185);
PicBoxArray[i].BackColor = Color.Aqua;
PicBoxArray[i].Parent = this.panBox;
PicBoxArray[i].Visible = true;
panBox.Controls.Add(PicBoxArray[i]);
PicBoxArray[i].Show();
i++;
}
}
I've thought about manage the whole program (it would be a game) with threads, but if there's some easier solution, it will be better.

When you create an array of objects, you're only allocating the space for the list of objects. You aren't creating the individual objects themselves. Add this line as the first line inside your while loop:
PicBoxArray[i] = new PictureBox();

Related

c# - "Loop-animation" through a string list

I'm building a very simple C# WPF Application. What the app does is that when I press a button it shows a value randomly from an string list.
What I would like to achieve is a animation where it quickly scrolls through all the values from the list (displaying in a label) and then start to slow down and stop on a random string from the list. Almost as a "spinning/scrolling/flashing wheel of text".
I'm still very new to programming (second day of C# learning) so would be glad if someone could point me in the right direction. Loop with timer?
List<string> randomStrings = new List<string>();
public MainWindow()
{
InitializeComponent();
randomString.Add("Abcd");
randomString.Add("Water");
randomString.Add("Moon");
randomString.Add("Pizza");
randomString.Add("Winter");
randomString.Add("Orange");
MyRandomStrings.ItemsSource = randomString; //Showing which strings in box.
}
public void GetRandomString()
{
Random r = new Random();
int index = r.Next(randomString.Count);
string myRandomString = randomString[index]; //Fetch a random string
Result.Content = myRandomString; //Sets the label. I want this to be "animated".
}
private void Button_Click(object sender, RoutedEventArgs e)
{
GetRandomString();
}
You could use a Task.Delay to create a delay and still create sequential code instead of a statemachine or timers etc.
Here is an example:
It uses a Label and a Button on a WindowsForm. So with a little ajustment, you can make it work on your configuration.
public partial class MainWindow : Form
{
private List<string> _randomStrings = new List<string>();
private Random _rnd = new Random(DateTime.UtcNow.Millisecond);
public MainWindow()
{
InitializeComponent();
_randomStrings.Add("Abcd");
_randomStrings.Add("Water");
_randomStrings.Add("Moon");
_randomStrings.Add("Pizza");
_randomStrings.Add("Winter");
_randomStrings.Add("Orange");
}
private async void button1_Click(object sender, EventArgs e)
{
// determine the rollcount. (some initial rolls for the rolling effect)
int rollCount = 50 + _rnd.Next(_randomStrings.Count);
int index = 0;
// roll it.......
for (int i = 0; i < rollCount; i++)
{
// just use a modulo on the i to get an index which is inside the list.
index = i % _randomStrings.Count;
// display the current item
label1.Text = _randomStrings[index];
// calculate a delay which gets longer and longer each roll.
var delay = (250 * i / rollCount);
// wait some. (but don't block the UI)
await Task.Delay(delay);
}
MessageBox.Show($"and.... The winner is... {_randomStrings[index]}");
}
}

Taking Control of WPF elements

I have an integer array declared in a class. Using that array, a PolyLine is drawn on a grid. The elements of the array are the y-coordinates. The array has to updated continuously and then the values are to be displayed. My idea of implementing this is to have two threads. One to update the values of the array and the other to print PolyLine on the grid.
Updating the array is not a problem. When I try to print PolyLine on the grid using the code below, an exception is thrown with the following message: "The calling thread cannot access this object because a different thread owns it."
Kindly suggest a workaround for this problem using thread. Perhaps, an event may be used instead of thread? I am open to suggestions. Any help is appreciated!
public partial class MainWindow : Window
{
Polyline Wave = new Polyline();
public MainWindow()
{
InitializeComponent();
Refresh();
DisplyOnGrid();
ThreadStart child = new ThreadStart(DisplyOnGrid);
Thread _DisplayOnGrid = new Thread(child);
_DisplayOnGrid.Priority = ThreadPriority.Highest;
_DisplayOnGrid.Start();
}
private void DisplyOnGrid()
{
Wave.Stroke = Brushes.Yellow;
Wave.StrokeThickness = 1.25;
for (int i = 0; i < DisplayGrid.Width; i++)
{
Wave.Points.Add(new Point(i, 50));
}
DisplayGrid.Children.Add(Wave);
}
private void Refresh()
{
DisplayGrid.Children.Clear();
}
}
You are getting Error because you are trying to access object which is owned by UI/Dispatcher thread
try something like this:
private void DisplyOnGrid()
{
Dispatcher.Invoke(new Action(()=>
{
Wave.Stroke = Brushes.Yellow;
Wave.StrokeThickness = 1.25;
for (int i = 0; i < DisplayGrid.Width; i++)
{
Wave.Points.Add(new Point(i, 50));
}
DisplayGrid.Children.Add(Wave);
}
))};

C# - Updating a Chart in Real-Time

Bear with me, I'm new to Stack Overflow, but have used it as a resource for a long time when researching methods of programming that I'm not fond with.
I read up a tutorial on how to create a graph in a C# Windows Forms Application, and was attempting to find out how to make it update itself in real-time if I ever need to use the graph to plot the total amount of data in a sensor. To test it out, I'm using a timer, ticking at every second (1000ms). And before I can use this with a sensor, I'm having two values automatically increment by 1.
The current problem I'm facing is that the chart itself won't update, and only stays the way it was drawn when the form loaded. I thought it was because I have to redraw the chart with chart1.Update();, and I tried using that before/after recreating the chart every second. However, the result is the same regardless. I just wondered if there's something I haven't done or needs to be changed in order to update the chart in real-time.
This is where the code is at currently:
public partial class Form1 : Form
{
int a = 1;
int b = 2;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// Data arrays.
string[] seriesArray = { "Cats", "Dogs" };
int[] pointsArray = { a, b };
// Set palette.
this.chart1.Palette = ChartColorPalette.SeaGreen;
// Set title.
this.chart1.Titles.Add("Pets");
// Add series.
for (int i = 0; i < seriesArray.Length; i++)
{
// Add series.
Series series = this.chart1.Series.Add(seriesArray[i]);
// Add point.
series.Points.Add(pointsArray[i]);
}
}
private void timer1_Tick(object sender, EventArgs e)
{
a++;
b++;
// Data arrays.
string[] seriesArray = { "Cats", "Dogs" };
int[] pointsArray = { a, b };
// Set palette.
this.chart1.Palette = ChartColorPalette.SeaGreen;
// Set title.
this.chart1.Titles.Add("Pets");
// Add series.
for (int i = 0; i < seriesArray.Length; i++)
{
// Add series.
Series series = this.chart1.Series.Add(seriesArray[i]);
// Add point.
series.Points.Add(pointsArray[i]);
}
chart1.Update();
}
}
Your code has several problems:
The timer click event is not hooked up. I know that it isn't because otherwise you'd get an exception telling you that..
..you can add a series only once. You were doing it on each timer.Tick. This and all other setup commands should go into an initial method like the form load.
I have corrected the errors in the code below, but, obviously, the data don't make any sense yet.
Also: While I have added code to hook up the timer.Tick, the button.Click is not hooked up. Usually you let the designer do this by double-clicking the control to hook up the standard event of a control or by double-clicking the event in the control's event tab in the property page.
int a = 1;
int b = 2;
string[] seriesArray = { "Cats", "Dogs" };
private void Form1_Load(object sender, EventArgs e)
{
// Set palette.
this.chart1.Palette = ChartColorPalette.SeaGreen;
// Set title.
this.chart1.Titles.Add("Pets");
// Add series
this.chart1.Series.Clear();
for (int i = 0; i < seriesArray.Length; i++)
{
chart1.Series.Add(seriesArray[i]);
}
// hook up timer event
timer1.Tick += timer1_Tick;
}
private void timer1_Tick(object sender, EventArgs e)
{
a++;
b++;
// Data array
int[] pointsArray = { a, b };
for (int i = 0; i < seriesArray.Length; i++)
{
// Add point.
chart1.Series[i].Points.Add(pointsArray[i]);
}
}
private void button1_Click(object sender, EventArgs e)
{
timer1.Start();
}

I created an array of pictureBoxes how can i register for an event to all the pictureBoxes?

The code:
pbs = new PictureBox[8];
for (int i = 0; i < pbs.Length; i++)
{
pbs[i] = new PictureBox();
pbs[i].MouseEnter += Form1_MouseEnter;
pbs[i].MouseLeave += Form1_MouseLeave;
pbs[i].Size = new Size(100, 100);
pbs[i].Margin = new Padding(0, 0, 0, 60);
pbs[i].Dock = DockStyle.Top;
pbs[i].SizeMode = PictureBoxSizeMode.StretchImage;
Panel p = i < 4 ? panel1 : panel2;
p.Controls.Add(pbs[i]);
pbs[i].BringToFront();
}
I did:
pbs[i].MouseEnter +=
And when i clicked TAB it did: Form1_MouseEnter
It's not what i wanted.
I want that when i move with the mouse over each of the pictureBoxes area it will do something.
One event for all the pictureBoxes.
If i moved over pictureBox1 do something...pictureBox2 the same...
How can i do it ? I don't want to create 8 events for each pictureBox but one enter event for all.
You need simply write
pbs[i].MouseEnter += globalMouseEnterEvent;
of course you need to have a globalMouseEnterEvent somewhere in your code
public void globalMouseEnterEvent(object sender, System.EventArgs e)
{
....
}
However, another piece of information is needed when your work with event shared between numerous controls. You need to recognize the control that triggers the event. The control instance is passed using the sender parameter that you can cast to your appropriate control type, but what is needed is to give a unique identifier to your control. Like setting the Tag or Name properties when you build the control
for (int i = 0; i < pbs.Length; i++)
{
.....
pbs[i].Tag = "PB" + i.ToString()
...
}
so in the MouseEnter code you could write
public void globalMouseEnterEvent(object sender, System.EventArgs e)
{
PictureBox p = sender as PictureBox;
if(p.Tag.ToString() == "PB1")
.....
else if ......
}
dont use form1_event , copy it's code and rename it
pbs[i].MouseEnter += yourEventName
it's enough
What you're doing is absolute correct, you attach the handler to the event of each control in
turn, so that the same handler works for every PictureBox.
I guess your problem is that the method VS creates is named Form1_MouseEnter. This is completely irrelevant, what determines what a method will handle is the += operator, not its name. Just try to run your original code and it will do what you want.
It seems to be a bug in the C# editor though, as it should have named the automatically generated handler something more appropriate, but anyway, you can rename the method afterwards to reflect its true meaning.
I've tried to apply the tips from the others to your code:
pbs = new PictureBox[8];
for (int i = 0; i < pbs.Length; i++)
{
pbs[i] = new PictureBox();
pbs[i].MouseEnter += Picturebox_MouseEnter;
pbs[i].MouseLeave += PictureBox_MouseLeave;
pbs[i].Name = string.Concat("PB", i); //Added to identify each picturebox
pbs[i].Size = new Size(100, 100);
pbs[i].Margin = new Padding(0, 0, 0, 60);
pbs[i].Dock = DockStyle.Top;
pbs[i].SizeMode = PictureBoxSizeMode.StretchImage;
Panel p = i < 4 ? panel1 : panel2;
p.Controls.Add(pbs[i]);
pbs[i].BringToFront();
}
And the handlers:
private void Picturebox_MouseEnter(object sender, EventArgs e)
{
PictureBox pb = sender as PictureBox;
if (pb != null)
{
if (pb.Name == "PB2")
{
//Do PB2 specific task
}
//Your code when mouse enters one of the pictureboxes
//Use Name property to determine wich one, if needed
}
}
private void PictureBox_MouseLeave(object sender, EventArgs e)
{
//Your code when mouse leaves one of the pictureboxes
//Use Name property to determine wich one, if needed
}

Update multiple pictureboxes at the same time

I have four PictureBoxes (each PictureBox represents one dice) and a Timer that changes every 100ms source pictures (loaded in memory as List<Bitmap> imagesLoadedFromIncludedResources).
Code:
private List<PictureBox> dices = new List<PictureBox>();
private void timer_diceImageChanger_Tick(object sender, EventArgs e)
{
foreach (PictureBox onePictureBox in dices)
{
oneDice.WaitOnLoad = false;
onePictureBox.Image = //... ;
oneDice.Refresh();
}
}
I need to change all the images at once - at this moment, you can see that the images are changing from left to right with a small delay.
I tried variant with one Thread for each PictureBox (using Control.Invoke method from this answer) - it is visually little better but not perfect.
You can try to suspend form's layout logic:
SuspendLayout();
// set images to pictureboxes
ResumeLayout(false);
Parallel.ForEach
(
dices,
new ParallelOptions { MaxDegreeOfParallelism = 4 },
(dice) =>
{
dice.Image = ...;
dice.WaitOnLoad = false;
dice.Refresh();
}
);
The problem is that UI controls can only be accessed from the UI thread. If you want to use this approach, you must create a copy of your PictureBoxes and then replace the UI ones once the operation is done.
Another approach would be creating two PictureBoxes, with the first one just on the top of the other one (hiding the latter)... you change all the images and then, once the processing is complete, you iterate all the ones in the back putting them on the top which would result in a lesser delay.
I'd approach this differently - it's been a while since I've played with WinForms stuff, but I'd probably take more control over the rendering of the images.
In this example I've got the images all in one source bitmap stacked vertically, stored as a resource in assembly:
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Timer timer;
private Bitmap dice;
private int[] currentValues = new int[6];
private Random random = new Random();
public Form1()
{
InitializeComponent();
this.timer = new Timer();
this.timer.Interval = 500;
this.timer.Tick += TimerOnTick;
this.dice = Properties.Resources.Dice;
}
private void TimerOnTick(object sender, EventArgs eventArgs)
{
for (var i = 0; i < currentValues.Length; i++)
{
this.currentValues[i] = this.random.Next(1, 7);
}
this.panel1.Invalidate();
}
private void button1_Click(object sender, EventArgs e)
{
if (this.timer.Enabled)
{
this.timer.Stop();
}
else
{
this.timer.Start();
}
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.White);
if (this.timer.Enabled)
{
for (var i = 0; i < currentValues.Length; i++)
{
e.Graphics.DrawImage(this.dice, new Rectangle(i * 70, 0, 60, 60), 0, (currentValues[i] - 1) * 60, 60, 60, GraphicsUnit.Pixel);
}
}
}
}
}
The source is here if it helps: http://sdrv.ms/Wx2Ets

Categories

Resources