I have a scene with two different PlotCubes, wich have to be displayed individually. What is the best procedure to hide and show the PlotCubes. I have tried with remove, but this seems to alter the PlotCube-objects.
The code-lines are:
IlPanel1.Scene.add(PlotCube1)
IlPanel1.Scene.add(PlotCube2)
Now both cubes are visible. Now I want to show only PlotCube2:
IlPanel1.Scene.Remove(PlotCube1)
IlPanel1.Scene.add(PlotCube2)
For switching back to PlotCube1:
IlPanel1.Scene.Remove(PlotCube2)
IlPanel1.Scene.add(PlotCube1)
But this does not work. The remove statement seems to delete the whole object. Is there a way to add/remove elements as LinePlots, SurfacePlots, PlotCubes without affecting the original object?
Use the Visible property of the plot cube to toogle its visibility:
// stores the current state. (You may even use one of the ILPlotCube.Visible flags directly)
bool m_panelState = false;
// give the plot cubes unique tags so we can find them later.
private void ilPanel1_Load(object sender, EventArgs e) {
ilPanel1.Scene.Add(new ILPlotCube("plotcube1") {
Plots = {
Shapes.Circle100, // just some arbitrary content
new ILLabel("Plot Cube 1")
},
// first plot cube starts invisible
Visible = false
});
ilPanel1.Scene.Add(new ILPlotCube("plotcube2") {
Plots = {
Shapes.Hemisphere, // just some content
new ILLabel("Plot Cube 2")
}
});
}
// a button is used to switch between the plot cubes
private void button1_Click(object sender, EventArgs e) {
m_panelState = !m_panelState;
SetState();
}
// both plot cubes are made (un)visible depending on the value of the state variable
private void SetState() {
ilPanel1.Scene.First<ILPlotCube>("plotcube1").Visible = m_panelState;
ilPanel1.Scene.First<ILPlotCube>("plotcube2").Visible = !m_panelState;
ilPanel1.Refresh();
}
The important part is to call Refresh() on the panel in order for the modifications to show up immediately.
Note that, in general it is better to keep drawing objects around if you may need them later again. Instead of removing them from the scene graph and later recreating a similar object, setting the object to Visible = false is much faster and does not incur the rather large cost of (re)creating graphical objects.
Related
Original Question: Edit properties of Graphics object c#
I've managed to create the object models and converted the entire program to use this structure so many thanks for the template there. My only problem is, I need to use threading to implement the color-changing feature. I have implemented the following code but have no luck:
private void pictureBox6_Click(object sender, EventArgs e)
{
running = !running;
}
public void redgreenMethod()
{
while (true)
{
if (running != true) continue;
if (flag == false)
{
flag = true;
Shape x = shapes[methodCounter - 1];
x.pen = Red;
}
else
{
flag = false;
Shape x = shapes[methodCounter];
x.pen = Green;
}
Thread.Sleep(500);
}
}
Surely the code inside the "if" statement changes the pen for the most recently added shape? I've entered a breakpoint and it shows that the pen does change constantly.. however, I don't see this being implemented on the Canvas?!
Where am I going wrong?!
From my previous comment:
Most likely redgreenMethod() is running in the main UI thread and the
tight loop is preventing the controls from updating themselves. Use a
TIMER instead. Set its Interval property to 500, and handle the Tick()
event. You don't need the while loop then. Instead of running you just
toggle the Enabled state of the Timer.
Here's what that might look like in code:
private void pictureBox6_Click(object sender, EventArgs e)
{
timer1.Enabled = !timer1.Enabled;
}
private void timer1_Tick(object sender, EventArgs e)
{
flag = !flag;
shapes[methodCounter - 1].pen = flag ? Green : Red;
}
You made these comments in an answer to your previous question:
Once a line has been drawn via the graphics: "g.DrawLine", the
properties of this line are then inaccessible. I need to find out how
to actually access these properties so I can use them inside the
Thread method.
In short, you DON'T access the properties of the line that has already been drawn. What you would do is update the color in the Shape (which you're already doing), and then call Invalidate() against the control that needs to be redrawn, such as pictureBox6.Invalidate(). This will force the Paint() event to fire again where everything will then be re-drawn, presumably with the new color that was set in the Shape properties.
I don't know if I still have missing on this but whenever you want to add PictureBox, you just find it on Toolbox and drag it on the UI. But what I did is I coded it by adding PictureBox then what I want is I have to move it to the right. I coded it on a Timer and this is how I do.
private void enemyMove_Tick(object sender, EventArgs e)
{
GameMenu menu = new GameMenu();
if (menu.cmbox_Level.Text.Equals("Easy"))
{
this.Controls.Add(menu.EnemyTank);
menu.EnemyTank.Width = 26;
menu.EnemyTank.Height = 32;
menu.EnemyTank.BackgroundImage = Properties.Resources.Tank_RIGHT_v_2;
menu.EnemyTank.BackgroundImageLayout = ImageLayout.Stretch;
menu.EnemyTank.Left += 2;
}
}
Don't ask me if the Timer is started. Yes it already started, it was coded on the Form. But somehow when I start the program it doesn't move. But then I tried adding PictureBox, this time I tried dragging it to UI then add an Image on it, then coded it by moving to the right. When I start the program it works. But what I want is that whenever I start the button it will just make the random PictureBox move to the right. Idk what's the missing in here.
I made some assumptions. If I my assumptions aren't correct, please update your post for details. Following content is copied from my comment.
A new GameMenu will always create a new EnemyTank.
A EnemyTank's default Left value is fixed;
Your enemyMove_Tick is keep creating GameMenu and EnemyTank.
That is, every tick, a EnemyTank is created and value changed to "default value plus 2" in your code.
To avoid it, you have to keep EnemyTank instance somewhere instead of keep creating it.
Here's a example.
GameMenu m_menu;
void YourConstructor()
{
// InitializeComponent();
// something else in your form constructor
m_menu = new GameMenu();
m_menu.EnemyTank.Width = 26;
m_menu.EnemyTank.Height = 32;
m_menu.EnemyTank.BackgroundImage = Properties.Resources.Tank_RIGHT_v_2;
m_menu.EnemyTank.BackgroundImageLayout = ImageLayout.Stretch;
this.Controls.Add( m_menu.EnemyTank );
}
private void enemyMove_Tick(object sender, EventArgs e)
{
if (menu.cmbox_Level.Text.Equals("Easy"))
{
m_menu.EnemyTank.Left += 2;
}
}
I'm making a system to balance calls inside of the OnGUI method of a EditorWindow.
I'm doing the following:
public void Update()
{
Repaint();
}
Inside of my OnGUI method I'm calling this Balancer. I have one list with the callbacks (List).
So the idea is simple, some callvaxc
I'm skipping some repaint frames for the callback that has the complete GUI, and calling on each repaint for other callbacks (for example, a marquee label or dispalying gifs).
By some reason, this error happens "Getting control 0's position in a group with only 0 controls when doing repaint"
private int m_repaintCounter;
public void Draw()
{
Event e = Event.current;
try
{
foreach (var action in m_actions)
{
try
{
// Test 1
// MainAction is a class that inherits from Action (class MainAction : Action)
if (action is MainAction)
{
bool isDesignedType = e.rawType == EventType.Repaint || e.rawType == EventType.Layout;
if (isDesignedType)
++m_repaintCounter;
if (!(m_repaintCounter == 20 && isDesignedType))
continue;
else
m_repaintCounter = 0;
}
// Test 2
action.Value();
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
}
catch
{
// Due to recompile the collection will modified, so we need to avoid the exception
}
}
But if I comment the "Test 1" everythings works fine.
On the ctor of the class we need to specify a callback to a GUI method, for example:
public Balancer(Action drawAction)
{
m_actions = new List<Action>();
m_actions.Add(drawAction);
}
So we could do easily (inside the EditorWindow):
private Balancer m_balancer;
public void OnEnable()
{
m_balancer = new Balancer(Draw);
}
public void Draw()
{
// This block will be called every 20 repaints as specified on the if statment
GUILayout.BeginHorizontal("box");
{
GUILayout.Button("I'm the first button");
GUILayout.Button("I'm to the right");
// This marquee will be called on each repaint
m_balancer.AddAction(() => CustomClass.DisplayMarquee("example"));
}
GUILayout.EndHorizontal();
}
// Inside of the Balancer class we have
// We use System.Linq.Expressions to identify actions that were added already
private HashSet<string> m_alreadyAddedActions = new HashSet<string>();
public void AddAction(Expression<Action> callback)
{
if(!m_alreadyAddedActions.Add(callback.ToString()))
return;
m_actions.Add(callback.Compile());
}
I can't figure this out. I couldn't find any information on the internet. Can anyone help me?
Ok, so, OnGui (IMGui) is awful to work with. If you aren't using it for an editor script, use the new 4.6 UI (UGui) instead.
Now then. The problem.
OnGui is called at least twice every frame. One of those is to calculate layouts and the other is to actually draw stuff ("repaint").
If the number of things, size of things, or anything else changes between these two calls then Unity will error with "Getting control 0's position in a group with only 0 controls when doing repaint."
That is: you cannot change UI state in the IMGui system at any point after Layout and before Repaint.
Only, only, only change state (and thereby which objects are being drawn) during Event.current == EventType.Repaint and only, only, only change state for the next frame (alternatively, do the changes during Event.current == EventType.Layout, provided that this same, new state will result in the same code path during Repaint). Do not, under any circumstances, make changes during Repaint that were not present during the previous Layout.
A table called floorLayout has the original cordinates of the objects stored. These details are showed in a 2D picturebox with basic 2D shapes.
A slave of above table gets updated real time for new cordinates of the objects and these new locations should be also udpated into the 2D shapes (by changing colour, location etc).
I am certainly new to graphics. So following are couple of questions I would like to clarify.
Do I need a backgroundworker with a thread to handle updates to 2D graphics?
Is there any other approach for this scenario?
Edit after helpful comments:
There's a table with a basic seating plan details.
Seat Number, Seat Type (represented by an eclipse or a square), Original Seat Location
When a seat is occupied or reserved, the reference shape in the picture box colour must be changed.
Seats can be in different sections. However at times a certain seat can be coupled with another seat. When a seat is coupled with another, its current location becomes the location of its couple seat (location remain original). Both seats' colour changes.
When decouple, the secondary seat location changes back to its original location and colour changes.
It means for each DML transaction that seating lsyout has an impact. Thats what I want to manage without compromising performance.
The application has three parts. Set up (login is a part of set up), Seating allocation, Graphical layout. Although it is in C#, the model is in 3-tier layered architecture for future web extensibility (if required). Plus having services and data access separately gives lots of freedom n easy to manage.
Given this scenario what're my options?
Edit2:[2014/07/01]
While trying out the Bitmap based drawing for my original question, I have come across an issue related to my threading. I am posting here, as it's infact related to the discussion I had with the capable answerers.
public void bgWorker_DoWork(object sender, DoWorkEventArgs d)
{
//insert seats (sql call via service/access interaces) to table
AddSeats();
}
//this doesn't get fired----<<<-------
public void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs p)
{
txtFlag.Text = p.ProgressPercentage.ToString();
}
//this works fine
public void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs r)
{
if (r.Cancelled)
{
txtFlag.Text = "Failed";
}
else
{
//load datagridview using datatable returned by select query
GetSeats();
//draw the bitmap with a loop over the datagridview
DrawFuntion();
//bgWorker.ReportProgress(prog, txtFlag.Text);
}
}
One major issue:
1. does this really make sense that I use bgworker to do the insertion to database, when it's completed I am calling loadgridview and draw methods?
2. I infact think, it's best to call draw method within bgworker, but I can't figure out how (logically, and functional flow-wise)
3. When I tried to run DrawFunction() within DoWork(), it just threw the biggest cross thread error: that access to UI controls in the form which are created using a different thread is not allowed.
How can I make sense of this?
Here is the layout I would go for, considering the results of our chat:
Given the time constraint of the current project, keep it in Winforms, but keep WPF in mind for a future revision.
Drawing a thousand seats is not a big problem, but in order to keep the GUI responsive it should be done by a background worker like this:
Create two Bitmap properties:
public Bitmap bmp_Display { get; set; }
public Bitmap bmp_Buffer { get; set; }
In the display Panel's Paint event you simply dump the bmp_Display onto the panel like this:
e.Graphics.DrawImage(bmp_Display, Point.Empty);
This is a single command and will happen real fast.
To create the updated floorplan the background thread draws the seats onto the bmp_Buffer, maybe like this:
foreach (Seat s in SeatList)
{
e.Graphics.FillRectangle(seatBrushes[s.State],
new Rectangle(s.Location, s.Size);
e.Graphics.DrawString(s.Name, seatFont, Brushes.Black, s.Location);
}
When it is done, it dumps it onto the bmp_Display like this:
bmp_Display = (Bitmap)bmp_Buffer.Clone();
To do that you should assure thread safety, maybe by a lock.
Finally the display Panel is invalidated.
The details will depend on the data structure and the business logic you will be using. If you will transfer only the changes, use them to update the data structure and still draw them all. You can Initialize the buffer Bitmap with a nice image of the floorplan with the tables and other things.
If needed you can create a helper app to act as a floorplan editor which would create the seats data structure and map it to the floorplan coordinates of the seats.
Here is an example of how the background worker can update the bitmap. Note that the error handling is practically non-existent; also only one of the locks should be necessary. And you need to get at the data somehow. The Seat class is just a dummy, too.
List<SolidBrush> seatBrushes = new List<SolidBrush>()
{ (SolidBrush)Brushes.Red, (SolidBrush)Brushes.Green /*..*/ };
public Bitmap bmp_Display { get; set; }
public Bitmap bmp_Buffer { get; set; }
public class Seat
{
public string Name { get; set; }
public int State { get; set; }
public Point Location { get; set; }
//...
}
private void drawFloorplan(List<Seat> seats)
{
Graphics G = Graphics.FromImage(bmp_Buffer);
Font sFont = new Font("Consolas", 8f);
Size seatSize = new Size(32, 20);
foreach (Seat s in seats)
{
G.FillRectangle(seatBrushes[s.State], new Rectangle(s.Location, seatSize));
G.DrawString(s.Name, sFont, Brushes.Black, s.Location);
}
G.Dispose();
sFont.Dispose();
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if ((worker.CancellationPending == true))
{
e.Cancel = true;
}
else
{
// get the seat data..
List<Seat> seats = new List<Seat>();
if (seats.Count > 0)
{
drawFloorplan(seats);
try { bmp_Display = (Bitmap)bmp_Buffer.Clone(); }
catch { /*!!just for testing!!*/ }
//lock(bmp_Display) { bmp = (Bitmap) bmp_Buffer.Clone(); }
}
}
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{ this.tbProgress.Text += "Cancelled!"; }
else if (!(e.Error == null))
{ this.tbProgress.Text += ("Error: " + e.Error.Message); }
else
{ panel1.Invalidate(); }
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
try { e.Graphics.DrawImage(bmp, Point.Empty); } catch {/*!!just for testing!!*/ }
//lock (bmp){ e.Graphics.DrawImage(bmp, Point.Empty); }
}
Edit 2 A few words about thread safety: The point of TS is to ensure that no two threads try to access the same object at the same time. Looking at the code one could wonder how it could happen, since it is only upon its completion that the BW thread will trigger the Invalidate. However things are more complicated with threads. They always are! Here for example the problems will come in when the system intself triggers an invalidate at a time the BW thread is dumping its result into the display bitmap. (The system is free to do so, whenever it sees a reason to get the screen updated.)
To avoid this one of the concurring threads should block its access while it is working with the resource. I suggest the do_Work method. Chances are that you never encounter a problem in your tests. My testbed ran up to 30 times per second (!) and after a few minutes it did. The try - catch only prevents crashing. For production code the whole situation should be avoided by using a lock.
Careful with lock though: 'lock blocks' and can create deadlocks, when used too generously..
In my winforms application written in C#, I need to synchronize the timing of 2 GIF-images placed on 2 pictureboxes. That means, one picturebox has one animated GIF-image and the second picturebox has the same animated GIF-image, but with different colors. Both images have to be displayed in the same frame-sequence, when the second picturebox appears.
Based on King King's answer in this thread, I implemented this extension methods:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void _buttonTest_Click(object sender, EventArgs e)
{
_pictureBox2.Visible = !_pictureBox2.Visible;
if (_pictureBox2.Visible)
_pictureBox2.SynchronizeWith(_pictureBox1);
}
}
public static class PictureBoxServices
{
public static void SetPictureFrameIndex(this PictureBox pictureBox, int index)
{
pictureBox.Image.SelectActiveFrame(new FrameDimension(pictureBox.Image.FrameDimensionsList[0]), index);
pictureBox.Image = pictureBox.Image; // restart on selected index
}
public static int GetPictureFrameIndex(this PictureBox pictureBox)
{
throw new NotImplementedException();
}
public static void SynchronizeWith(this PictureBox pictureBoxThis, PictureBox pictureBoxToSynchronizeWith)
{
pictureBoxThis.SetPictureFrameIndex(pictureBoxToSynchronizeWith.GetPictureFrameIndex());
}
}
I have 2 problems:
I dont know how to implement GetPictureFrameIndex
SetPictureFrameIndex is not working. It sets and displays the selected frame, but the next displayed frame is always with frameindex = 1, not frameindex = index + 1
Can someone help me please?
Even a hack-solution using reflection would be OK (if using reflection is needed).
There is a hack to a similar problem that could help you: Pause a GIF in a form.
Also, you could try resetting the images at the same time. Place the image in a temporary variable, assign both pictureboxes' images to null and then reassign the images at the same time.
a hack solution might be to offload the current images into temporary variables, set a temporary value for the image and then set it back to the temp variable. This forces each image to refresh from it's start point.
Just enumerate through each picturebox with that and it should refresh to be almost in sync, depending on how many you have and how long the operation takes I suppose.
assuming you have a picturebox named pic.
var loc = pic.ImageLocation;
pic.ImageLocation = "";
pic.ImageLocation = loc;