I am attempting to render some Image objects in a canvas (Layout) on the main form of my application which is called Main. I am using C# and WPF to create the application but am unable to get the images to render from within another class but works without problems in the Main form partial class.
public static void renderStarscape(int density = 200)
{
Main main = new Main();
Random random = new Random();
for (int x = 0; x < density; x++)
{
int starSize = random.Next(1, 10);
int starOpacity = random.Next(10, 30);
int starX = random.Next(0, 800);
int starY = random.Next(0, 500);
Image Star = new Image();
Star.Name = (x < 10) ? "star_0" + x : "star_" + x;
Star.Source = streamImage("star_background.png");
Star.Height = starSize;
Star.Width = starSize;
Star.Opacity = (double)starOpacity / 100;
main.Layout.Children.Add(Star);
Canvas.SetLeft(Star, starX);
Canvas.SetTop(Star, starY);
Canvas.SetZIndex(Star, 0);
}
}
Any help would be great, thanks
You are creating an all new instance of Main in the method which is not same as the one shown on UI.
Instead pass on the instance of the Canvas to the method and draw on that something like this -
public static void renderStarscape( Canvas layout, int density = 200)
{
// Use layout and remove the Main object initialization from the method.
layout.Children.Add(Star);
}
The reason its working in partial declaration of class is that you are using the same instance and not creating the new one for Main().
Related
I create a contact manager. The user can already enter some and they are stored in a file and re-opened when the program is started. Each contact is an object of my Person class.
When launching the program (in Load()) I created a for loop until all contacts have been explored (contacts are stored when opened in a Person table)
So now I come to my problem:
I have a panel that is scrollable (I have enabled the option) and I would like every 50 pixels in height, that a new panel is created with name, first name, email and phone number of my contacts and a pictureBox.
Except, I would like to be able to do it dynamically instead of creating the same thing more than 50 times and repeating the same code 50 times
Because for the moment I have done this:
for(int i = 0; i < contacts.Count; i++) //Afficher les contacts
{
if(!panel_contact1.Visible)
{
panel_contact1.Visible = true;
label_prenom_nom1.Text = contacts[i].Prenom + " " + contacts[i].Nom;
label_email1.Text = contacts[i].mail;
label_tel1.Text = contacts[i].tel;
pictureBox1.Image = Image.FromFile(contacts[i].pathImage);
}
else if(!panel_contact2.Visible)
{
panel_contact2.Visible = true;
label_prenom_nom2.Text = contacts[i].Prenom + " " + contacts[i].Nom;
label_email2.Text = contacts[i].mail;
label_tel2.Text = contacts[i].tel;
pictureBox2.Image = Image.FromFile(contacts[i].pathImage);
}
}
It's the code only for the first two contacts and I don't want to repeat it up to 100 times.
So my question is:
How to create panels, with in each of the labels and a pictureBox, every 50px in a panel.
Thank you for reading, if you just have advice said always the same if you all have the code I'm a taker especially since I think it should be easy to do because the content of the labels are already dynamically teaching.
Thank you.
On WinForms, you can use this:
int x = 0;
int y = 0;
int delta = 10;
for ( int i = 0; i < contacts.Count; i++ )
{
// Create picture box
var picture = new PictureBox();
picture.Image = Image.FromFile(contacts[i].pathImage);
picture.Location = new Point(x, y);
picture.Size = new Size(picture.Image.Width, picture.Image.Height);
int dx = picture.Width + delta;
// Create name label
var labelName = new Label();
labelName.AutoSize = true;
labelName.Location = new Point(x + dx, y);
labelName.Font = new Font(labelName.Font, FontStyle.Bold);
labelName.Text = contacts[i].Prenom + " " + contacts[i].Nom;
// Create mail label
var labelMail = new Label();
labelMail.AutoSize = true;
labelMail.Location = new Point(x + dx, y + labelName.Height);
labelMail.Text = contacts[i].mail;
// Create phone label
var labelPhone = new Label();
labelPhone.AutoSize = true;
labelPhone.Location = new Point(x + dx, y + labelName.Height + labelMail.Height);
labelPhone.Text = contacts[i].tel;
// Add controls
panel.Controls.Add(picture);
panel.Controls.Add(labelName);
panel.Controls.Add(labelMail);
panel.Controls.Add(labelPhone);
// Iterate
int dy1 = labelName.Height + labelMail.Height + labelPhone.Height;
int dy2 = picture.Height;
y += Math.Max(dy1, dy2) + delta;
}
But you may prefer create a custom control where you put a picture box and three labels designed as you want with colors, font size, bolding, margin, borderstyle and so on, with Height at 50.
Add new user custom control with Project > Add > User control and choose a file name like PersonControl.
public partial class PersonControl : UserControl
{
public PersonControl()
{
InitializeComponent();
}
public PersonControl(Person person) : this()
{
pictureBox.Image = Image.FromFile(person.pathImage);
labelName.Text = person.Prenom + " " + person.Nom;
labelMail.Text = person.mail;
labelPhone.Text = person.tel;
}
}
int x = 0;
int y = 0;
for ( int i = 0; i < contacts.Count; i++ )
{
var control = new PersonControl(contacts[i]);
control.Location = new Point(x, y);
panel.Controls.Add(control);
y += control.Height;
}
You should take care of the file image size that must be the same for all and the same as the picture box else you need to manage that by resizing for example.
How to resize an Image C#
If you're using windows forms, create a user control with a constructor using the Person object, set the labels and picture boxes to the info of that person. In the main loop you posted, create a new instance of this and set it's position to 0, i * 50 to place it under the previous one.
Example:
for(int i = 0; i < contacts.Count; i++)
{
YourUserControl u1 = new YourUserControl(pass the person object);
Panel1.Controls.Add(u1);
u1.Location = new Point(0, i * 50);
}
This depends on the display technolgy you are using (WinForms, WPF/UWP, ASP.NET, other).
In Windows Forms you just create the elements and add them to the container. The designer wroks on it's own part of the partial class. The designer code is run with InitializeComponents() in the constructor. Anything it can do, you can do. And you can easily look at it.
In WPF/UWP stuff is a bit more complicated. The designer does not work on code, but on XAML, a dedciated markup language. You are not supposed to manually add anything to the UI from the code. WPF/UWP and XAML were designed with the MVVM pattern in mind. And dealing with lists of things is what it does best. While you can use other patterns, generally that looses 90% of it's power and runs into issues at every other corner.
For ASP.Net it would depend on wich pattern you use. While not originally designed for it, MVC has been extremely popular with WebApplication. So much so, it is almost synonimous with WebApplications and ASP.NET. However this does not look like a web Application.
The end goal is a somewhat playable memory game. Currently, I'm stuck on a rendering problem. I have the following classes:
Field, which is an abstract UserControl:
public abstract class Field : UserControl
{
protected PictureBox _pictureBox;
public Field()
{
_pictureBox = new PictureBox();
_pictureBox.Image = Properties.Resources.empty;
_pictureBox.SizeMode = PictureBoxSizeMode.StretchImage;
_pictureBox.BorderStyle = BorderStyle.FixedSingle;
this.Controls.Add(_pictureBox);
}
// ...
// some abstract methods, not currently important
}
MemoryField, which derives from Field:
public class MemoryField : Field
{
public MemoryField(Form parent, int xPos, int yPos, int xSize, int ySize)
{
_pictureBox.ClientSize = new Size(xSize, ySize);
_pictureBox.Location = new Point(xPos, yPos);
_pictureBox.Parent = parent;
}
// ...
}
And finally, MainForm which is an entry point for my application:
public partial class MainForm : Form
{
private readonly int fieldWidth = 100; // 150 no rendering problems at all
private readonly int fieldHeight = 100;
public MainForm() { InitializeComponent(); }
private void MainForm_Load(object sender, EventArgs e)
{
for (int y = 0; y < 6; y++) // 6 rows
{
for (int x = 0; x < 10; x++) // 10 columns
{
Field field = new MemoryField(this,
x * (fieldWidth + 3), // xPos, 3 is for a small space between fields
labelTimer.Location.Y + labelTimer.Height + y * (fieldHeight + 3), // yPos
fieldWidth,
fieldHeight);
this.Controls.Add(field);
}
}
}
}
Here's where my problem lies:
In those for loops I'm trying to generate a 6x10 grid of Fields (with each containing a PictureBox 100x100 px in size). I do that almost successfully, as my second field is not rendered correctly:
Only thing I found that works (fixes the problem completely) is making field bigger (i.e. 150px). On the other hand, making it smaller (i.e. 50px) makes the problem even bigger:
Maybe useful information and things I've tried:
My MainForm is AutoSize = true; with AutoSizeMode = GrowAndShrink;
My MainForm (initially) doesn't contain any components except menuStrip and label
I tried changing PictureBox.Image property, that didn't work.
I tried creating the grid with just PictureBox controls (not using Field as a PictureBox wrapper), that did work.
I tried placing labelTimer in that "problematic area" which does fix the problem depending on where exactly I put it. (because field positioning depends on labelTimer 's position and height)
I tried relaunching visual studio 2017, didn't work.
Of course, I could just change the size to 150px and move on, but I'm really curious to see what's the root of this problem. Thanks!
The easiest thing to do to fix the problem is something you've already tried - using directly a PictureBox instead of a Field. Now, considering that you only use Field to wrap a PictureBox, you could inherit from PictureBox instead of just wrapping it.
Changing your classes to these will fix the issue as you've noticed:
public abstract class Field : PictureBox {
public Field() {
Image = Image.FromFile(#"Bomb01.jpg");
SizeMode = PictureBoxSizeMode.StretchImage;
BorderStyle = BorderStyle.FixedSingle;
Size = new Size(100, 100);
}
// ...
// some abstract methods, not currently important
}
public class MemoryField : Field {
public MemoryField(Form parent, int xPos, int yPos, int xSize, int ySize) {
ClientSize = new Size(xSize, ySize);
Location = new Point(xPos, yPos);
}
// ...
}
The real reason it was not working has to do with both sizing and positioning of each Field and their subcomponents. You should not set the Location of each _pictureBox relatively to its parent MemoryField, but rather change the Location of the MemoryField relatively to its parent Form.
You should also set the size of your MemoryField to the size of its child _pictureBox otherwise it won't size correctly to fit its content.
public class MemoryField : Field {
public MemoryField(Form parent, int xSize, int ySize) {
_pictureBox.ClientSize = new Size(xSize, ySize);
// I removed the setting of Location for the _pictureBox.
this.Size = _pictureBox.ClientSize; // size container to its wrapped PictureBox
this.Parent = parent; // not needed
}
// ...
}
and change your creation inner loop to
for (int x = 0; x < 10; x++) // 10 columns
{
Field field = new MemoryField(this,
fieldWidth,
fieldHeight);
field.Location = new Point(x * (fieldWidth + 3), 0 + 0 + y * (fieldHeight + 3)); // Set the Location here instead!
this.Controls.Add(field);
}
I have a few buttons to add on the form. In the code I'm setting up some button properties:
class DigitButton : Button
{
private static int digitBtnTag;
public DigitButton()
: base()
{
this.Size = new Size(30, 30);
this.Tag = digitBtnTag;
this.Text = (this.Tag).ToString();
this.Margin = new Padding(2);
this.Padding = new Padding(2);
digitBtnTag++;
}
}
In the MainForm.cs I have
for (int i = 0; i < dgtBtns.Length; i++)
{
dgtBtns[i] = new DigitButton();
dgtBtns[i].Click += new EventHandler(this.digitButtonClick);
digitPanel.Controls.Add(dgtBtns[i]);
}
So when I launch a program I see all my buttons in the one place: (0;0) on digitPanel despite property Margin. So why don't all these buttons automaticly "push" each other in the different directions? And how to make it?
Have you tried using a FlowLayout Panel ?
Also, this video might help:
Windows Forms Controls Lesson 5: How to use the FlowLayout Panel
that's not the way controls works in c#. i'm guessing you programed at java a bit because the layout in jave works that whay, but in c# just do
for (int i = 0; i < dgtBtns.Length; i++)
{
dgtBtns[i] = new DigitButton();
dgtBtns[i].Location = new Point(50, 50 * i); // Multiplying by i makes the location shift in every loop
dgtBtns[i].Click += new EventHandler(this.digitButtonClick);
digitPanel.Controls.Add(dgtBtns[i]);
}
you'll have to figure out the location parameters by trying and see
You need to define Left and Top then add the button height or width each time you loop to position your buttons correctly i.e.
int bTop=0;
int bLeft=0;
for (int i = 0; i < dgtBtns.Length; i++)
{
dgtBtns[i] = new DigitButton();
dgtBtns[i].Click += new EventHandler(this.digitButtonClick);
dgtBtns[i].Top = bTop;
bTop += dgtBtns[i].Height;
digitPanel.Controls.Add(dgtBtns[i]);
}
Hope you can help. Stuck on a simple problem for some, I'm a noob. What in trying to do is get an image objects in Silverlight/C# to drop randomly from the top of the canvas, at the moment its going right to left.
This is the from the object class.
namespace LOLWordGame
{
public class LetterA : ContentControl, IGameEntity
{
private int speed = 0;
public LetterA()
{
Image LetterImage = new Image();
LetterImage.Height = 45;
LetterImage.Width = 45;
LetterImage.Source = new BitmapImage(new Uri("images/a.png", UriKind.RelativeOrAbsolute));
this.Content = LetterImage;
Random random = new Random();
Canvas.SetLeft(this, -20);
Canvas.SetTop(this, random.Next(250, 850)); //randomly
speed = random.Next(1, 5);
}
public void Update(Canvas c)
{
Move(Direction.Down);
if (Canvas.GetLeft(this) < 100)
{
c.Children.Remove(this);
}
}
public void Move(Direction direction)
{
Canvas.SetLeft(this, Canvas.GetLeft(this) - speed);
}
}
}
Thanks in advance.
For a solution: maybe you should use the Canvase.SetTop Method instead of the SetLeft method? Hope this helps.
Secondary.. I'm sure following code is not the solution to your problem but I refactored it a bit. Try using collection initializers. You have a method Move that you only call once and the method has only one line of code: no reason to make that a method in my opinion. Also the method takes in a parameter but you do not use it inside the method.
public class LetterA : ContentControl, IGameEntity
{
private int speed = 0;
public LetterA()
{
var letterImage = new Image()
{
Height = 45,
Width = 45,
Source = new BitmapImage(new Uri("images/a.png", UriKind.RelativeOrAbsolute))
};
Content = letterImage;
var random = new Random();
Canvas.SetLeft(this, -20);
Canvas.SetTop(this, random.Next(250, 850));
speed = random.Next(1, 5);
}
public void Update(Canvas c)
{
Canvas.SetLeft(this, Canvas.GetLeft(this) - speed);
if (Canvas.GetLeft(this) < 100)
c.Children.Remove(this);
}
}
I'm trying to draw 10 rectangles, but when I use g.DrawRectangle() it is drawing a cross as shown below:
I'm creating Vertex objects that contain a getRectangle() function which returns a Rectangle object for that vertex.
I was hoping to create these objects and show them as Rectangles on the pictureBox.
Here's my code
private System.Drawing.Graphics g;
private System.Drawing.Pen pen1 = new System.Drawing.Pen(Color.Blue, 2F);
public Form1()
{
InitializeComponent();
pictureBox.Dock = DockStyle.Fill;
pictureBox.BackColor = Color.White;
}
private void paintPictureBox(object sender, PaintEventArgs e)
{
// Draw the vertex on the screen
g = e.Graphics;
// Create new graph object
Graph newGraph = new Graph();
for (int i = 0; i <= 10; i++)
{
// Tried this code too, but it still shows the cross
//g.DrawRectangle(pen1, Rectangle(10,10,10,10);
g.DrawRectangle(pen1, newGraph.verteces[0,i].getRectangle());
}
}
Code for Vertex class
class Vertex
{
public int locationX;
public int locationY;
public int height = 10;
public int width = 10;
// Empty overload constructor
public Vertex()
{
}
// Constructor for Vertex
public Vertex(int locX, int locY)
{
// Set the variables
this.locationX = locX;
this.locationY = locY;
}
public Rectangle getRectangle()
{
// Create a rectangle out of the vertex information
return new Rectangle(locationX, locationY, width, height);
}
}
Code for Graph class
class Graph
{
//verteces;
public Vertex[,] verteces = new Vertex[10, 10];
public Graph()
{
// Generate the graph, create the vertexs
for (int i = 0; i <= 10; i++)
{
// Create 10 Vertexes with different coordinates
verteces[0, i] = new Vertex(0, i);
}
}
}
Looks like an exception in your draw loop
last call to:
newGraph.verteces[0,i]
fails with OutOfRangeException
you shoul iterate not to i <= 10, but to i < 10
Red Cross Indicates that an Exception has been thrown, you are not seeing it because it's being handled. Configure Visual Studio to break on exception throw to catch it.
An exception has been thrown. At first look your code:
for (int i = 0; i <= 10; i++)
will generate an IndexOutOfRangeException because verteces has 10 items but it will cycle from 0 to 10 (included so it'll search for 11 elements). It depends on what you want to do but you have to change the cycle to (removing the = from <=):
for (int i = 0; i < 10; i++)
or to increment the size of verteces to 11.