Method is not working inside onGUI() method - c#

The thing that I want to do is writing a method which can call in onGUI() method.
I wrote this method. However when I run the program , method did not show the effect.
private void ShowSubPartsOnClick(float x, float y, float widthLABEL, float heigth, HumanBodyPart bodyPart)
{
x = x + 14;
for(int i = 0; i < bodyPart.SubParts.Count; i++)
{
y = y + 14;
GUI.Label(new Rect(x+14,y,widthLABEL,heigth), bodyPart.SubParts[i].EnglishTitle);
if(GUI.Button(new Rect(x, y, 14, heigth),"+"))
{
ShowSubPartsOnClick(x, y, widthLABEL, heigth, bodyPart.SubParts[i]);
}
}
}
}
private void OnGUI()
{
GUI.Label(new Rect(text.transform.position.x+14, text.transform.position.y, text.rectTransform.sizeDelta.x, 14),bodyVisualizer.BodyData.Body.SubParts[0].EnglishTitle);
if(GUI.Button(new Rect(text.transform.position.x, text.transform.position.y, 14, 14), "+"))
{
ShowSubPartsOnClick(text.transform.position.x, text.transform.position.y, text.rectTransform.sizeDelta.x, 14, bodyVisualizer.BodyData.Body.SubParts[0]);
}
}
How can I fix this or what is the problem?

The challenge here is functions like GUI.Label and GUI.Button must be invoked directly from OnGUI to work:
From Unity Forums: "The only place you can draw/create GUI elements is by triggering them from inside an OnGUI function."
Given the recommendation there, one solution is to run an iterative depth first search via a while loop. See attached example.
That being said, I'd highly recommend using Unity Canvas instead of OnGUI. It's much more powerful and its programmatic logic is not constrained to a single function.
OnGUI Snippet:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HumanBodyPart
{
public string EnglishTitle;
public List<HumanBodyPart> SubParts;
public bool IsExpanded;
public int DrawDepth;
public HumanBodyPart(string title, HumanBodyPart[] subParts)
{
this.EnglishTitle = title;
this.SubParts = new List<HumanBodyPart>();
this.SubParts.AddRange(subParts);
this.IsExpanded = false;
this.DrawDepth = 0;
}
}
public class Script : MonoBehaviour
{
[SerializeField]
Text text;
HumanBodyPart mainBodyPart;
private void Start()
{
HumanBodyPart subSubSubBodyPart = new HumanBodyPart("SubSubSubBodyPart", new HumanBodyPart[] { });
HumanBodyPart subSubBodyPart1 = new HumanBodyPart("SubSubBodyPart1", new HumanBodyPart[] { subSubSubBodyPart });
HumanBodyPart subSubBodyPart2 = new HumanBodyPart("SubSubBodyPart2", new HumanBodyPart[] { });
HumanBodyPart subBodyPart = new HumanBodyPart("SubBodyPart", new HumanBodyPart[] { subSubBodyPart1, subSubBodyPart2});
mainBodyPart = new HumanBodyPart("BodyPart", new HumanBodyPart[] { subBodyPart });
UpdateDrawDepths(mainBodyPart);
}
private void UpdateDrawDepths(HumanBodyPart currentBodyPart, int currentDrawDepth=0)
{
currentBodyPart.DrawDepth = currentDrawDepth;
foreach (HumanBodyPart bodyPart in currentBodyPart.SubParts)
{
UpdateDrawDepths(bodyPart, currentDrawDepth + 1);
}
}
private void OnGUI()
{
float spacing = 30;
float x = text.transform.position.x + spacing;
float y = text.transform.position.y;
int drawDepth = 0;
List<HumanBodyPart> nextPartsToRender = new List<HumanBodyPart>(new HumanBodyPart[] { mainBodyPart });
while (nextPartsToRender.Count > 0)
{
HumanBodyPart currentPart = nextPartsToRender[0];
GUI.Label(new Rect(currentPart.DrawDepth * spacing + x, y, 200, 20), currentPart.EnglishTitle);
nextPartsToRender.RemoveAt(0);
if (GUI.Button(new Rect(x - spacing + currentPart.DrawDepth * spacing, y, 20, 20), "+"))
{
currentPart.IsExpanded = true;
}
if (currentPart.IsExpanded)
{
nextPartsToRender.InsertRange(0, currentPart.SubParts);
}
y += spacing;
}
}
}

Related

How should I return a list that was previously set without setting it again?

I have two classes, Main and Grid. Grid simply makes a grid of square pixels. In my Main class, I want to get the list that was create in the Grid class. I managed to figure it out, but I'm wondering if there's a way to optimize the code.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Diagnostics;
using System.Collections.Generic;
namespace teeeest
{
public class Grid
{
Texture2D image;
Color color;
int rows;
int columns;
float outerThickness;
float innerThickness;
Vector2 size;
Vector2 origin;
Vector2 dotSize;
List<Pixel> pixels = new List<Pixel>(0);
public Grid(Texture2D image, int rows, int columns, float outerThickness, float innerThickness, Vector2 size, Vector2 origin, Vector2 dotSize, Color color)
{
this.dotSize = dotSize;
this.origin = origin;
this.color = color;
this.image = image;
this.rows = rows;
this.columns = columns;
this.outerThickness = outerThickness;
this.innerThickness = innerThickness;
this.size = size;
}
public void Update()
{
float sizeX = size.X / (columns - 1);
float sizeY = size.Y / (rows - 1);
for (int i = 0; i < rows; i++)
{
for (int g = 0; g < columns; g++)
{
Pixel p = new Pixel(image, 3, new Vector2((g * sizeX) + origin.X, sizeY * i + origin.Y), new Vector2(image.Width / 2, image.Height / 2), color);
pixels.Add(p);
}
}
}
public virtual void Draw(SpriteBatch hspritebatch, List<Grid> grids)
{
foreach (Pixel p in pixels)
{
hspritebatch.Draw(
texture: p.getImage(),
position: p.getPosition(),
sourceRectangle: null,
p.getColor(),
rotation: 0,
origin: new Vector2(image.Width / 2, image.Height),
scale: new Vector2(dotSize.X * .02f, dotSize.Y * .02f),
SpriteEffects.None,
0);
}
}
public Texture2D getImage()
{
return image;
}
public Vector2 getPosition()
{
return origin;
}
public Vector2 getOrigin()
{
return new Vector2(image.Width / 2, image.Height);
}
public Color getColor()
{
return color;
}
public List<Pixel> getList()
{
Update(); # This seems unnecessary. Is it?
return pixels;
}
}
}
The problem lies in the getList() function. In order to return the correct pixel list that was edited in the Update function, my solution there is to call that function right before returning the list. However, this seems costly for no reason. Is there a way around this without calling the Update function, or is this the only way?
I realize there's been posts similar to this, but I just don't understand them. I'm very much a beginner at coding. Here is my Main class.
using System;
using System.Diagnostics;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace teeeest
{
public class Game1 : Game
{
private GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;
private SpriteFont font;
private Texture2D ball;
private Texture2D square;
private Color color = Color.White * .1f;
private Vector2 MouseCoords;
private Vector2 winMiddle;
private Vector2 ballOrigin;
private bool leftDown;
private bool eDown;
private int winWidth;
private int winHeight;
List<Line> lines = new List<Line>(0);
List<Grid> grids = new List<Grid>(0);
List<Pixel> pixels = new List<Pixel>(0);
List<Pixel> test = new List<Pixel>(0);
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
_graphics.PreferredBackBufferWidth = 800;
_graphics.PreferredBackBufferHeight = 600;
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
ball = Content.Load<Texture2D>("ball");
square = Content.Load<Texture2D>("square");
font = Content.Load<SpriteFont>("File");
ballOrigin = new Vector2(ball.Width / 2, ball.Height / 2);
winWidth = _graphics.PreferredBackBufferWidth;
winHeight = _graphics.PreferredBackBufferHeight;
winMiddle = new Vector2(winWidth / 2, winHeight / 2);
}
protected override void Update(GameTime gameTime)
{
lines.Clear();
grids.Clear();
pixels.Clear();
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
if (Mouse.GetState().LeftButton == ButtonState.Released)
{
leftDown = false;
}
if (Mouse.GetState().LeftButton == ButtonState.Pressed && !leftDown)
{
Pixel g = new Pixel(ball, 5, MouseCoords, new Vector2(ball.Width / 2, ball.Height / 2), Color.Blue);
pixels.Add(g);
}
if (Keyboard.GetState().IsKeyUp(Keys.E))
{
eDown = false;
}
if (Keyboard.GetState().IsKeyDown(Keys.E) && !eDown)
{
color *= 1.1f;
eDown = true;
}
MouseCoords = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
Grid q = new Grid(ball, 10, 10, 7, 3, new Vector2(500, 500), new Vector2(30, 30), new Vector2(.2f, .2f), Color.White);
grids.Add(q);
# Here is where I'm calling the getList() function.
System.Console.WriteLine(q.getList()[7].getPosition());
# Here is where I'm calling the getList() function.
foreach (Line s in lines)
{
s.Update();
}
foreach (Grid gh in grids)
{
gh.Update();
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
_spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
foreach (Line l in lines)
{
l.Draw(_spriteBatch, lines);
}
foreach (Grid g in grids)
{
g.Draw(_spriteBatch, grids);
}
foreach (Pixel p in pixels)
{
_spriteBatch.Draw(
texture: p.getImage(),
position: p.getPosition(),
sourceRectangle: null,
p.getColor(),
rotation: 0,
origin: p.getOrigin(),
scale: new Vector2(.02f, .02f),
SpriteEffects.None,
0);
}
_spriteBatch.DrawString(font,
MouseCoords.ToString(),
new Vector2 (winWidth - 100, 10),
Color.White,
rotation: 0,
origin: new Vector2(0, 0),
scale: new Vector2(1, 1),
SpriteEffects.None,
0);
_spriteBatch.End();
base.Draw(gameTime);
}
}
}
You could only call Update() when the List is EMPTY?
public List<Pixel> getList()
{
if (pixels.Count == 0)
{
Update(); // now it only gets called when pixels is EMPTY
}
return pixels;
}
This type of check may need to be done in Update() as well if it can be called directly from other places so you don't end up with more Pixel instances in it than you were expecting.

How to make a shape move with the arrow keys?

using System.Drawing;
using System.Windows.Forms;
public partial class Form1 : Form
{
int vx = 5;
int vy = 5;
int bx = 0;
int by = 50;
int px = 93;
public Form1()
{
InitializeComponent();
timer1.Interval = 100;
timer1.Start();
}
public class Ball
{
public int X;
public int Y;
public int W;
public int H;
public Ball(int x, int y, int w, int h)
{
X = x;
Y = y;
W = w;
H = h;
}
}
public class Paddle
{
public int X;
public int Y;
public int W;
public int H;
public Paddle(int x, int y, int w, int h)
{
X = x;
Y = y;
W = w;
H = h;
}
}
public class Brick
{
public int X;
public int Y;
public int W;
public int H;
public Brick(int x, int y, int w, int h)
{
X = x;
Y = y;
W = w;
H = h;
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
int[] brickxs = { 0, 51, 102, 153, 204, 255, 306, 357, 408, 459, 510, 561, 612, 663, 714, 765 };
int bc = 0;
SolidBrush blueBrush = new SolidBrush(Color.Blue);
Ball b = new Ball(55, 55, 25, 25);
Paddle p = new Paddle(93, 377, 130, 30);
Brick br = new Brick(20, 20, 51, 20);
br.X = 0;
while (bc < 16)
{
br.X = brickxs[bc];
System.Drawing.SolidBrush myBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Red);
System.Drawing.Graphics formGraphics;
formGraphics = this.CreateGraphics();
formGraphics.FillRectangle(myBrush, new Rectangle(br.X, 0, 49, 20));
myBrush.Dispose();
formGraphics.Dispose();
bc = bc + 1;
}
Rectangle ball = new Rectangle(bx, by, b.W, b.H);
Rectangle paddle = new Rectangle(px, p.Y, p.W, p.H);
//Rectangle brick = new Rectangle(br.X, br.Y, br.W, br.H);
e.Graphics.FillEllipse(blueBrush, ball);
e.Graphics.FillRectangle(blueBrush, paddle);
//e.Graphics.FillRectangle(blueBrush, brick);
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.Right)
{
px += 5;
}
}
private void MoveTimer_Tick(object sender, EventArgs e)
{
Invalidate();
}
private void timer1_Tick(object sender, EventArgs e)
{
bx = bx + vx;
by = by + vy;
if (px <= 0)
{
px = 0;
}
if (px >= 771)
{
px = 771;
}
WallCollision();
floorandCeilingCollision();
Invalidate();
}
public void WallCollision()
{
if (bx >= 771)
{
vx = -5;
}
if (bx <= 0)
{
vx += 5;
}
}
public void floorandCeilingCollision()
{
if (by >= 420)
{
vy = -5;
}
if (by <= 0)
{
vy = 5;
}
}
}
I am creating a game and I need some help.
In my code have classes for each of the parts of the game: the ball, paddle and bricks. The array positions the bricks.
I want to move the paddle (which just a rectangle) left and right with the arrow keys. I tried to use the key down method but it did not work.
Could you suggest any solutions or point out anything that I left out?
Personally, I use e.KeyCode instead of e.KeyData, try this first.
Make sure your Form is focused, and not a picturebox or something else you might have in the game. Because you try to call the KeyDown event for your Form, not for a control inside your Form.
I never used a Paint event, are you sure it is called? It might be the case that your game registeres the movement but never shows the changes to you. I usually have a separate method for drawing and I call it every time there is a change, you should try this too.
If nothing works, try debugging. Set a break point in your KeyDown method to see if it is called. If it does, set it in the Paint method. This one will surely be called once, at runtime, but if you click "Continue" on that time and try to move your object. If it is not called any other time, then here is your answer :)
Please update me with what you find after trying this things, and ask me what to do next if you get stuck or simply don't know what else there is to do :)

Changing GUI Button text when clicked the UI Button

I want to change GUI button text when I click a UI button. onGUI() method which I have written, is below.
This method is used to create new buttons when the button is clicked and to show the Sub-parts of a tree structure.
If I show you the change I want to make in the code, for example, when I click UI Latin Button, currentPart.EnglishTitle changes like currentPart.LatinTitle. I also mentioned this part as a comment in the code
currentPart is a HumanBodyPart object. HumanBodyPart is a class that stores my nodes of my tree structure.
I give you the necessary parts of my code. If there is a missing part in the code that I have given, I can edit the desired parts.
onGUI() method is here...
private void OnGUI()
{
Vector3 scale = new Vector3(Screen.width / nativeSize.x, Screen.height / nativeSize.y, 1.0f);
GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, scale);
float spacing = 25;
float x = 7 + spacing;
float y = 63;
HumanBodyPart mainBodyPart = bodyVisualizer.BodyData.Body.SubParts[0];
List<HumanBodyPart> nextPartsToRender = new List<HumanBodyPart>(new HumanBodyPart[] { mainBodyPart });
List<HumanBodyPart> allPartsToRender = new List<HumanBodyPart>(new HumanBodyPart[] { mainBodyPart });
scrollPosition = GUI.BeginScrollView(new Rect(7, y, 264, 485), scrollPosition, new Rect(7, y, 528, scrollPosition_y));
while (nextPartsToRender.Count > 0)
{
HumanBodyPart currentPart = nextPartsToRender[0];
nextPartsToRender.RemoveAt(0);
//The place I want to change above is the place "currentPart.English" in a bottom line
if (GUI.Button(new Rect(currentPart.DrawDepth * spacing + x, y, 200, 20), currentPart.EnglishTitle))
{
if (!currentPart.IsClicked)
{
currentPart.IsClicked = true;
HumanBodyVisualizer.ShowMode showModeFullBody = HumanBodyVisualizer.ShowMode.Invisible;
bodyVisualizer.ShowBody(showModeFullBody);
AllSubPartsAndRoot.Insert(AllSubPartsAndRoot.Count, currentPart);
addAllSubPartsOfClickButton(currentPart, AllSubPartsAndRoot, AllSubPartsAndRoot.Count - 1);
HumanBodyVisualizer.ShowMode showModeCurrentPart = HumanBodyVisualizer.ShowMode.LowTransparent;
for (int i = 0; i < AllSubPartsAndRoot.Count; i++)
{
bodyVisualizer.ShowBodyPart(showModeCurrentPart, AllSubPartsAndRoot[i]);
}
}
else
{
currentPart.IsClicked = false;
List<HumanBodyPart> RemoveBodyParts = new List<HumanBodyPart>();
RemoveBodyParts.Insert(0, currentPart);
addAllSubPartsOfClickButton(currentPart, RemoveBodyParts, 1);
for (int i = 0; i < RemoveBodyParts.Count; i++)
{
if (AllSubPartsAndRoot.Contains(RemoveBodyParts[i]))
{
bodyVisualizer.ShowBodyPart(HumanBodyVisualizer.ShowMode.Invisible, RemoveBodyParts[i]);
AllSubPartsAndRoot.Remove(RemoveBodyParts[i]);
}
}
if (AllSubPartsAndRoot.Count == 0)
{
bodyVisualizer.ShowBody(HumanBodyVisualizer.ShowMode.LowTransparent);
}
else
{
for (int ii = 0; ii < AllSubPartsAndRoot.Count; ii++)
{
bodyVisualizer.ShowBodyPart(HumanBodyVisualizer.ShowMode.LowTransparent, AllSubPartsAndRoot[ii]);
}
}
}
}
if (currentPart.SubParts.Count != 0)
{
if (GUI.Button(new Rect(x - spacing + currentPart.DrawDepth * spacing, y, 20, 20), ">"))
{
if (!currentPart.IsExpanded)
{
currentPart.IsExpanded = true;
}
else
currentPart.IsExpanded = false;
}
if (currentPart.IsExpanded)
{
nextPartsToRender.InsertRange(0, currentPart.SubParts);
allPartsToRender.InsertRange(allPartsToRender.Count - 1, currentPart.SubParts);
scrollPosition_y = allPartsToRender.Count * spacing;
}
}
y += spacing;
index++;
}
// End the scroll view that we began above.
GUI.EndScrollView();
}
public Button turkishButton;
public Button englishButton;
public Button latinButton;
The above is UI buttons in the script.
In general you shouldn't use OnGUI but rather the Unity UI system.
You could just have a string with the text to display and do e.g.
private string onGuiButtonLabel;
public Button turkishButton;
public Button englishButton;
public Button latinButton;
private void Awake()
{
turkishButton.onClick.AddListener(()=>{onGuiButtonLabel = "Turkish";})
englishButton.onClick.AddListener(()=>{onGuiButtonLabel = "English";})
latinButton.onClick.AddListener(()=>{onGuiButtonLabel = "Latin";})
}
and then in OnGUI you use
if (GUI.Button(new Rect(currentPart.DrawDepth * spacing + x, y, 200, 20), onGuiButtonLabel))
or maybe use a kind of Dictionary with an enum like
public Language
{
English,
Turkish,
Latin
}
private Dictionary<Language, string>() titles;
private Language curentLanguage;
// or wherever you want to initialize it
private void Awake()
{
titles = new Dictionary<Language, string>()
{
{Language.English, currentPart.EnglishTitle}
{Language.Turkish, currentPart.TurkishTitle}
{Language.Latin, currentPart.LatinTitle}
}
turkishButton.onClick.AddListener(()=>{curentLanguage = Language.Turkish;})
englishButton.onClick.AddListener(()=>{curentLanguage = Language.English;})
latinButton.onClick.AddListener(()=>{curentLanguage = Language.Latin;})
}
and then in OnGUI use
if (GUI.Button(new Rect(currentPart.DrawDepth * spacing + x, y, 200, 20), titles[currentLanguage]))

How to make GUI Rect child of an object?

I have this problem, that I draw a new Rect with a GUI: it only draws now on screen. What I want to do is to make this drawing a child of an object, so I can hide this with SetActive(false). This will be only available when players will pause and open Inventory. The game is 2D for Android.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Inventory : MonoBehaviour
{
public int SlotsX, SlotsY;
public GUISkin Skin;
public List<Item> inventory = new List<Item>();
public List<Item> slots = new List<Item>();
private ItemDatabase database;
void Start()
{
for (int i = 0; i < (SlotsX * SlotsY); i++)
{
slots.Add(new Item());
}
database = GameObject.FindGameObjectWithTag("Item Database").GetComponent<ItemDatabase>();
inventory.Add(database.Items[0]);
inventory.Add(database.Items[1]);
inventory.Add(database.Items[3]);
inventory.Add(database.Items[4]);
inventory.Add(database.Items[5]);
inventory.Add(database.Items[6]);
}
void OnGUI()
{
GUI.skin = Skin;
DrawInventory();
for (int i = 0; i < inventory.Count; i++)
{
GUI.Label(new Rect(10, i * 40, 200, 50), inventory[i].itemName);
}
}
void DrawInventory()
{
for (int x = 0; x < SlotsX; x++)
{
for (int y = 0; y < SlotsY; y++)
{
GUI.Box(new Rect(x * 180, y * 180, 160, 160), "", Skin.GetStyle("Slot"));
}
}
}
}
To set the parent of a GameObject in Unity programmatically, simply use this:
childGameObject.transform.SetParent(parentGameObject);
EDIT:
Turns out it isn't this simple with OnGUI. Follow Draco18s's advice and use the new GUI system.

PictureBox updates only on resize

Trying to display a graph on a Form application.
Created a PictureBox control and initialized this class with its value.
On resize, graph always updates; on mouse scroll, it hardly does.
It's GraphBox , PictureBox control, inside a GraphBoxPanel, Panel control.
This the class:
public struct DLT_measure_item
{
public DateTime ts;
public float value;
public int id;
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct dlt_ser_meas
{
public byte msg_id; // 'D'
public byte meas_count; // Number of measures
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public byte[] port; // Module ID (4b) + Port ID (4b)
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public float[] meas; // measure
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public byte[] msg_end;
}
public class manageGraph
{
private PictureBox box;
public bool displayGrid = true;
private int horAxisMin_p = 0;
private int horAxisMax_p = 300;
private float verAxisMin_p = 0;
private float verAxisMax_p = 40;
public int horAxisMin
{
get { return this.horAxisMin_p; }
set
{
if (value < horAxisMax_p)
{
this.horAxisMin_p = value;
reDraw();
}
}
}
public int horAxisMax
{
get { return this.horAxisMax_p; }
set
{
if (value > horAxisMin_p)
{
this.horAxisMax_p = value;
reDraw();
}
}
}
public float verAxisMin
{
get { return this.verAxisMin_p; }
set
{
if (value < verAxisMax_p)
{
this.verAxisMin_p = value;
verPointPerUnit = graphArea.Height / (verAxisMax_p - this.verAxisMin_p);
}
}
}
public float verAxisMax
{
get { return this.verAxisMax_p; }
set
{
if (value > verAxisMin_p)
{
this.verAxisMax_p = value;
verPointPerUnit = graphArea.Height / (this.verAxisMax_p - verAxisMin_p);
}
}
}
Pen axes = new Pen(Color.Black, (float)1.5);
public int horAxisSpacing = 30;
public int verAxisSpacing = 20;
public int horAxis = 20;
public int verAxis = 20;
private float horPointPerUnit = 1;
private float verPointPerUnit = 1;
public int horAxisTickLen = 5;
public int verAxisTickLen = 5;
public bool horAxisShowTime = false;
private Rectangle graphArea = new Rectangle();
public void reDraw()
{
box.Image.Dispose();
Bitmap GraphBlankImage = new Bitmap(box.Width, box.Height);
box.Image = GraphBlankImage;
updatePointPerUnit();
drawGrid();
box.Refresh();
}
public manageGraph(PictureBox targetImageBoxbox)
{
box = targetImageBoxbox;
horAxisMin_p = 0;
horAxisMax_p = 300;
verAxisMin_p = 0F;
verAxisMax_p = 50F;
updatePointPerUnit();
}
private Point measToPoint(DLT_measure_item measure) {
Point coords = new Point();
coords.X = graphArea.Width - (int)(
((DateTime.Now - measure.ts).TotalSeconds + horAxisMin_p) * horPointPerUnit ) ;
coords.Y = graphArea.Height - (int)(
((measure.value - verAxisMin_p) * verPointPerUnit));
return coords;
}
public manageGraph(PictureBox targetImageBoxbox,
int xmin, int xmax, float ymin, float ymax)
{
box = targetImageBoxbox;
horAxisMin_p = xmin;
horAxisMax_p = xmax;
verAxisMin_p = ymin;
verAxisMax_p = ymax;
updatePointPerUnit();
}
private void updateGraphArea()
{
graphArea = new Rectangle(0, 0, box.Width - horAxis, box.Height - verAxis);
}
private void updatePointPerUnit()
{
updateGraphArea();
horPointPerUnit = graphArea.Width / (horAxisMax_p - horAxisMin_p);
verPointPerUnit = graphArea.Height / (verAxisMax_p - verAxisMin_p);
}
public void drawGrid()
{
//updatePointPerUnit();
using (Graphics g = Graphics.FromImage(box.Image))
{
// X axis
g.DrawLine(axes, graphArea.Left, graphArea.Bottom, box.Width, graphArea.Bottom);
// Y axis
g.DrawLine(axes, graphArea.Right + 1, graphArea.Top, graphArea.Right +1, graphArea.Bottom);
using (Font ArialFont = new Font("Arial", 10))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
// Put x labels
for (int i = 1; i <= (graphArea.Width / horPointPerUnit); i = i + (int)(horAxisSpacing / horPointPerUnit ) + 1)
{
g.DrawString((i).ToString(), ArialFont, Brushes.Black, graphArea.Width - ( i * horPointPerUnit) - 5, graphArea.Bottom +5);
g.DrawLine(axes, graphArea.Width - (i * horPointPerUnit), graphArea.Bottom + (horAxisTickLen / 2), graphArea.Width - (i * horPointPerUnit), graphArea.Bottom - (horAxisTickLen / 2));
}
// Put y labels
for (int i = 1; i <= (graphArea.Height / verPointPerUnit); i = i + (int)(verAxisSpacing / verPointPerUnit) +1)
{
g.DrawString((i).ToString(), ArialFont, Brushes.Black, graphArea.Right + 1 , graphArea.Height - (i * verPointPerUnit) - 8);
g.DrawLine(axes, graphArea.Width - (verAxisTickLen / 2), (i * verPointPerUnit), graphArea.Width + (verAxisTickLen / 2), (i * verPointPerUnit));
}
}
/*Put some random data*/
DLT_measure_item testmeas = new DLT_measure_item();
Point testGraphPoint = new Point();
testmeas.ts = DateTime.Now;
testmeas.value = 0;
testGraphPoint = measToPoint(testmeas);
g.FillEllipse(Brushes.Blue, testGraphPoint.X, testGraphPoint.Y, 4, 4);
for (double i = 0; i < 300; i++)
{
double x = i;
double freq = 10;
double y = 30 - (i/10);
testmeas.value = (float)y;
testmeas.ts = DateTime.Now.AddSeconds(-1 * i);
testGraphPoint = measToPoint(testmeas);
g.FillEllipse(Brushes.Red, testGraphPoint.X, testGraphPoint.Y, 2,2);
}
}
}
}
The initialization:
public DLThermalogMainForm()
{
InitializeComponent();
Bitmap GraphBlankImage = new Bitmap(GraphBox.Width, GraphBox.Height);
GraphBox.Image = GraphBlankImage;
myGraph = new manageGraph(GraphBox);
myGraph.drawGrid();
}
Those the handlers:
private void Form1_ResizeEnd(object sender, EventArgs e)
{
myGraph.reDraw();
OutputTextBox.AppendText("Resize." + Environment.NewLine);
}
private void GraphBox_MouseMove(object sender, MouseEventArgs e)
{
//Get the focus to have the wheel working
GraphBoxPanel.Focus();
}
private void GraphBoxPanel_MouseWheel(object sender, MouseEventArgs e)
{
// Change x axis max value to zoom in/out the graph
myGraph.horAxisMax += e.Delta/ 120;
myGraph.reDraw();
}
On resize event, it always redraws; on mouse wheel, it does quickly only with small horAxisMax values (does it makes sense???), but for larger, it takes many seconds to update, or doesn't at all.
Thank you very much
Change reDraw like this:
public void reDraw()
{
box.Image.Dispose();
Bitmap GraphBlankImage = new Bitmap(box.ClientSize.Width, box.ClientSize.Height);
updatePointPerUnit();
drawGrid(GraphBlankImage);
box.Image = GraphBlankImage;
}
and drawGrid like this:
public void drawGrid(Bitmap bmp)
{
//updatePointPerUnit(); //??
using (Graphics g = Graphics.FromImage(bmp))
{
...
...
...
}
}
Now the Bitmap with the grid should immediately show up in the PictureBox.
As mentioned a Graphics object is a tool to change an associated Bitmap. To pick it up the Bitmap should be assigned to the PictureBoxe's Image.
Also note, that unless your PictureBox has no Border, there is a small difference between the outer size (aka Bounds) and the inner size, the ClientRectangle / ClientSize. The Image should have the ClientSize
You may wonder why your original code doesn't work? After all an Image is a reference type, so changing it, as you did should be enough..
But looking deeper into the source code we find the reason:
The PictureBox's Image is a property and in its setter there is a call to InstallNewImage:
public Image Image {
get {
return image;
}
set {
InstallNewImage(value, ImageInstallationType.DirectlySpecified);
}
}
The same call is also in a few other places like Load or in the setter of ImageLocation. But changing the Image behind the scene alone will not force the PictureBox to make that call. A Refresh() should also do it.. And, as you found out, resizing it will also cause the PictureBox to pick up the changed data in the Image Bitmap..
The easiest way to force updating is simply to invalidate the control.
timer = new Timer();
timer.Interval = 200; //refreshes every 200 ms
timer.Tick += (sender,e) => targetImageBoxbox.Invalidate();
timer.Start();

Categories