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;
Related
I have a very simple program that for some reason has me stumped. I put it down, came back at it again this morning and I'm still stumped. First off, I'm aware this is not an ideal solution. I have two forms: Main and Log. The Main form has a button that adds to List _debugLog when clicked. When btnDebug is clicked, it opens the Log form, passing _debugLog to it. Everything is fine, the timer is setup and runs, everything is normal. The event log.UpdateLog() is triggered every 2.5 seconds to update the Log form with the updated log. However, mainFormLog.Count and _log.Count are always the same and they BOTH increase when btnAdd is clicked on the main form. How does _log have the new _debugLog (mainFormLog) from the tick event?
namespace Tool
{
public partial class Main : Form
{
private List<string> _debugLog = new List<string>();
public Main()
{
InitializeComponent();
}
private void btnAdd_Click(object sender, EventArgs e)
{
_debugLog.Add("message!");
}
private void btnDebug_Click(object sender, EventArgs e)
{
Log log = new Log(_debugLog);
log.Show();
Timer dt = new Timer();
dt.Interval = 2500;
dt.Enabled = true;
dt.Tick += delegate {
log.UpdateLog(_debugLog);
};
}
}
public partial class Log : Form
{
private List<string> _log;
public Log(List<string> log)
{
InitializeComponent();
_log = log;
}
public void UpdateLog(List<string> mainFormLog)
{
if (mainFormLog.Count > _log.Count)
{
MessageBox.Show("Log has been updated!");
}
else
{
MessageBox.Show("Nothing new!" + mainFormLog.Count.ToString() + " / " + _log.Count.ToString());
}
}
}
}
Well, you're passing the reference to the list from Main to Log, so it's actually the same list.
If you want a separate list that gets initialized with the list from Main you can use:
public Log(List<string> log)
{
InitializeComponent();
_log = new List<string>(log);
}
Maybe this helps to understand the difference between variables and references:
For a value type, the value is the information itself. For a reference
type, the value is a reference which may be null or may be a way of
navigating to an object containing the information.
For example, think of a variable as like a piece of paper. It could
have the value "5" or "false" written on it, but it couldn't have my
house... it would have to have directions to my house. Those
directions are the equivalent of a reference. In particular, two
people could have different pieces of paper containing the same
directions to my house - and if one person followed those directions
and painted my house red, then the second person would see that change
too. If they both just had separate pictures of my house on the paper,
then one person colouring their paper wouldn't change the other
person's paper at all.
All your variables _debugLog, mainFormLog, and _log are pointing to the same list in memory. You've only created one list, and when you assign a new variable to that list, it's just a pointer to some location in memory, it doesn't automatically create a new copy of the list.
I have a winforms application where user can select an image from a list of available images and the corresponding image is shown in a PictureBox. The images can be very huge with a minimum of 10MB. This obviously makes the rest of the UI unresponsive while the image loads. So I thought of loading the image on a separate thread using the following code:
private void LoadImage()
{
// loadViewerThread is a Thread object
if (loadViewerThread != null && loadViewerThread.IsAlive)
{
loadViewerThread.Abort(); // Aborting the previous thread if the user has selected another image
}
loadViewerThread = new Thread(SetViewerImage);
loadViewerThread.Start();
}
The SetViewerImage function is as below:
private void SetViewerImage()
{
if (pictureBox1.Image != null)
pictureBox1.Image.Dispose();
pictureBox1.Image = new Bitmap(/*Some stream*/);
}
After this the images load smoothly and the UI can also be accessed.
But if the user moves very fast between the set of images then a big red X mark comes up. This happens because I have that call to Dispose in SetViewerImage.
I have assigned an ErrorImage to the PictureBox but that ErrorImage is never shown in this case.
Questions:
Is there anything wrong with my thread implementation? Why does the
Image gets disposed?
Is there any way that I can display a different ErrorImage and not
the red X?
You need to manipulate controls in the UI thread. You can do this by using Control.Invoke() from a different thread.
The big bottleneck is creating the image from the stream, so you should be able to reorganize your method like this to keep the UI thread freed up:
private void SetViewerImage()
{
Bitmap image = new Bitmap(/* Some stream*/);
pictureBox1.Invoke(new Action(() =>
{
if (pictureBox1.Image != null)
{
pictureBox1.Image.Dispose();
pictureBox1.Image = null; // This might help? Only add if it does.
}
pictureBox1.Image = image;
}));
}
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..
I have a class Viewer that creates two linked FastColoredTextBoxes. I want the two boxes to scroll together horizontally. I have this code:
public class Viewer : Panel
{
public FastColoredTextBox HeaderRow = new FastColoredTextBox();
public FastColoredTextBox Editor = new FastColoredTextBox();
public Viewer(int _Top, int _Left, int _Height, int _Width, bool _HasHeaderRow, Control control)
{
this.Editor.Scroll += new ScrollEventHandler(Editor_Scroll);
}
void Editor_Scroll(object sender, ScrollEventArgs e)
{
if (e.ScrollOrientation == ScrollOrientation.HorizontalScroll)
{
this.HeaderRow.HorizontalScroll.Value = this.Editor.HorizontalScroll.Value;
}
this.HeaderRow.UpdateScrollbars();
}
}
It doesn't work. I've never tried to do attach events to controls in a class instance before. If I declare the controls in my form and attach a very similar event (minus the .this's) it works fine. Thank you.
i think that for the next time try to tell yourself " what could it be?" and maybe debug a little, like a breackpoint for example. as you probably understood, you had a little mistake in the line
this.HeaderRow.HorizontalScroll.Value = this.HeaderRow.HorizontalScroll.Value;
you meant to write
HeaderRow.HorizontalScroll.Value = Editor.HorizontalScroll.Value;
you just got mixed between the two or something, which happens to all of us. but the first thing i would do is to think and debug it, check the values and let someone look at it. only then post it here.
In my (Silverlight) weather app I am downloading up to 6 seperate weather radar images (each one taken about 20 mins apart) from a web site and what I need to do is display each image for a second then at the end of the loop, pause 2 seconds then start the loop again. (This means the loop of images will play until the user clicks the back or home button which is what I want.)
So, I have a RadarImage class as follows, and each image is getting downloaded (via WebClient) and then loaded into a instance of RadarImage which is then added to a collection (ie: List<RadarImage>)...
//Following code is in my radar.xaml.cs to download the images....
int imagesToDownload = 6;
int imagesDownloaded = 0;
RadarImage rdr = new RadarImage(<image url>); //this happens in a loop of image URLs
rdr.FileCompleteEvent += ImageDownloadedEventHandler;
//This code in a class library.
public class RadarImage
{
public int ImageIndex;
public string ImageURL;
public DateTime ImageTime;
public Boolean Downloaded;
public BitmapImage Bitmap;
private WebClient client;
public delegate void FileCompleteHandler(object sender);
public event FileCompleteHandler FileCompleteEvent;
public RadarImage(int index, string imageURL)
{
this.ImageIndex = index;
this.ImageURL = imageURL;
//...other code here to load in datetime properties etc...
client = new WebClient();
client.OpenReadCompleted += new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
client.OpenReadAsync(new Uri(this.ImageURL, UriKind.Absolute));
}
private void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
if (e.Error == null)
{
StreamResourceInfo sri = new StreamResourceInfo(e.Result as Stream, null);
this.Bitmap = new BitmapImage();
this.Bitmap.SetSource(sri.Stream);
this.Downloaded = true;
FileCompleteEvent(this); //Fire the event to let the app page know to add it to it's List<RadarImage> collection
}
}
}
As you can see, in the class above I have exposed an event handler to let my app page know when each image has downloaded. When they have all downloaded I then run the following code in my xaml page - but only the last image ever shows up and I can't work out why!
private void ImageDownloadedEventHandler(object sender)
{
imagesDownloaded++;
if (imagesDownloaded == imagesToDownload)
{
AllImagesDownloaded = true;
DisplayRadarImages();
}
}
private void DisplayRadarImages()
{
TimerSingleton.Timer.Stop();
foreach (RadarImage img in radarImages)
{
imgRadar.Source = img.Bitmap;
Thread.Sleep(1000);
}
TimerSingleton.Timer.Start(); //Tick poroperty is set to 2000 milliseconds
}
private void SingleTimer_Tick(object sender, EventArgs e)
{
DisplayRadarImages();
}
So you can see that I have a static instance of a timer class which is stopped (if running), then the loop should show each image for a second. When all 6 have been displayed then it pauses, the timer starts and after two seconds DisplayRadarImages() gets called again.
But as I said before, I can only ever get the last image to show for some reason and I can't seem to get this working properly.
I'm fairly new to WP7 development (though not to .Net) so just wondering how best to do this - I was thinking of trying this with a web browser control but surely there must be a more elegant way to loop through a bunch of images!
Sorry this is so long but any help or suggestions would be really appreciated.
Mike
You can use a background thread with either a Timer or Sleep to periodically update your image control.
Phạm Tiểu Giao - Threads in WP7
You'll need to dispatch updates to the UI with
Dispatcher.BeginInvoke( () => { /* your UI code */ } );
Why don't you add the last image twice to radarImages, set the Timer to 1000 and display just one image on each tick?