Binding Writeable bitmap to canvas - c#

I'm trying to write a simple WPF program to implement Conway's Game of Life.
My Window consists of a toolbar and a canvas with and embedded Image, on which I'm trying to display a writeable bitmap.
I have a button on the toolbar which, when clicked, updates a single generation and then displays the resulting bitmap on the canvas image. I update the image directly with
img.Source = wbmp;
This works without a problem.
However, I'd like to show the ongoing updates, without having to click the "Update" button each time. So I tried implementing a loop of 10 iterations. However, the updated image is only shown after the 10th iteration is completed (i.e. I don;t see the first 9 generations)
My understanding is that I need to bind the Image control to the Writeable bitmap in order to "force" an update each generation. I've tried this with the code below - but now nothing displays at all. Initially I found that the PropertyChanged event didn't seem to be firing but I had no method assigned so, I added PropertyChanged = delegate {}; (I did this because the Internet told me to!)
I am really not sure where I'm going wrong. I'm rather clueless about WPF and binding in particular. (Much of my code is adapted copy-pasta.) Any help would be greatly appreciated!
<Canvas Name ="canvas" Grid.Row="1" Background="LightGray">
<Image Name="img" Source="{Binding GoLImage}"/>
</Canvas>
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private ImageSource golImage; //writeable bitmap;
public ImageSource GoLImage
{
get { return golImage; }
set
{
golImage = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(GoLImage)));
}
}
}
private void Button_Run10_Click(object sender, RoutedEventArgs e)
{
BitmapPixelMaker bmp = new BitmapPixelMaker(1200, 800);
for (int i = 0; i < 10; i++)
{
goL.UpdateLifeGrid(); //goL is an instance of my class implementing the Game of Life
goL.LifeGridTobmp(bmp);
WriteableBitmap wbmp = bmp.MakeBitmap(96, 96);
//Trying to display bitmap
MyViewModel golDisplay = new MyViewModel();
golDisplay.GoLImage = wbmp; //This doesn't automatically display on each iteration
}
}

The for loop in your Button Click handler blocks the UI thread. And you have not assigned the view model instance to the DataContext property of the view.
Use a DispatcherTimer with a Tick event handler like shown below.
Do not create a new view model instance and a new WriteableBitmap in each cycle, but just modify the existing one - you should therefore change the view model property declaration to public WriteableBitmap GoLImage so that the ModifyBitmap method can access it.
private readonly MyViewModel golDisplay = new MyViewModel();
private readonly DispatcherTimer timer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(0.1)
};
public MainWindow()
{
InitializeComponent();
golDisplay.GoLImage = WriteableBitmap(1200, 800, 96, 96, PixelFormats.Default, null);
DataContext = golDisplay;
timer.Tick += OnTimerTick;
timer.Start(); // optionally, call Start/Stop in a Button Click handler
}
private void OnTimerTick(object sender, EventArgs e)
{
var bmp = new BitmapPixelMaker(1200, 800);
goL.UpdateLifeGrid();
goL.LifeGridTobmp(bmp);
ModifyBitmap(bmp); // write directly to golDisplay.GoLImage
}
An alternative to a DispatcherTimer might be an simple loop over an asynchronous and awaited update call.
Something like shown below, where the Update method would perform the CPU-intensive calculations and then return the new pixel buffer
private async void OnWindowLoaded(object sender, RoutedEventArgs e)
{
while (true)
{
var buffer = await Task.Run(() => game.Update());
bitmap.WritePixels(new Int32Rect(0, 0, game.Width, game.Height),
buffer, game.Stride, 0);
}
}

Related

Do you have to change the background image every second?

i just started learning c# two month ago, and i got this code for my current project (windows forms) to set the background image:
public FormMain()
{
this.BackgroundImage = Properties.Resources.image;
InitializeComponent();
var timer = new Timer();
////change the background image every second
timer.Interval = 1000;
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
//add image in list from resource file.
List<Bitmap> lisimage = new List<Bitmap>();
lisimage.Add(Properties.Resources.image);
var indexbackimage = DateTime.Now.Second % lisimage.Count;
this.BackgroundImage = lisimage[indexbackimage];
}
my question is: do you have to change the background image every second, or is it enough if i just write (i have only one single background image):
public FormMain()
{
this.BackgroundImage = Properties.Resources.image;
InitializeComponent();
}
cause it seems to work.
You would only need a timer like that if you were iterating through a series of images in order to create an animation.
What you have is good enough for setting the image once.
As AaronLS wrote, setting the background once would suffice. I'd go a step further and explain why the extra code you have makes very little sense (assuming this is the entire code).
void timer_Tick(object sender, EventArgs e)
{
//add image in list from resource file.
List<Bitmap> lisimage = new List<Bitmap>(); //this line creates a new list
lisimage.Add(Properties.Resources.image); //fill the NEWLY created list with the one image from the resources
//note, that resources are usually static, so it's always the same resource
var indexbackimage = DateTime.Now.Second % lisimage.Count; //choose an index from the list, but the list only contains that one image, so the index will always be 0
this.BackgroundImage = lisimage[indexbackimage]; //pick the same image that was set initially
}
As you can see, the code is rather nonsensical - it doesn't DO anything. It SEEMS someone wanted to create a mechanism to switch images every second, but even THAT is poorly coded.

Unable to reset ProgressBar

I am experimenting a behavior which makes me crazy.
I have a ProgressBar which represents the evolution of an import in database (in percents, from 0 to 100).
After the import is done (ProgressBar.Value = 100.0), I open a log window with a code which looks like this :
RadWindow window = new RadWindow()
{
//Set some properties
};
window.Closed += Log_Closed;
window.ShowDialog();
After the RadWindow is closed, I want to reset the ProgressBar. As you can see I use the function Log_Closed whose code is bellow :
private void Log_Closed(object sender, EventArgs e)
{
//pbImport.Value = pbImport.Minimum; (didn't work)
pbImport.Value = 0;
}
Note : pbImport is my progress bar.
The instruction in Log_Closed has no effect.
Before instruction :
After instruction :
Obviously, the progress bar is not updated in UI. I can't understand this. Thank you for your help.
Animations hold onto properties, in order to reset them in code, you have to remove the animation first so that the property is "released".
See https://msdn.microsoft.com/en-us/library/aa970493%28v=vs.110%29.aspx for information on how to set a property after an animation in WPF.
Resetting the progress Bar can be achieved by using an "if" loop and incrementing the progress bar.
You can set a bool value for the database process and then simply:
private void Log_Closed(object sender, EventArgs e)
{
//pbImport.Value = pbImport.Minimum; (didn't work)
pbImport.Value = 0;
if (database)
{
pbImport.Increment(100);
}
}
From Microsoft's documentation -
To remove a specific AnimationClock from a list of clocks, use the Controller property of the AnimationClock to retrieve a ClockController, then call the Remove method of the ClockController. This is typically done in the Completed event handler for a clock. Note that only root clocks can be controlled by a ClockController; the Controller property of a child clock will return null. Note also that the Completed event will not be called if the effective duration of the clock is forever. In that case, the user will need to determine when to call Remove.
In the example below I demonstrate setting up an event handler that runs when the animation is complete and removes the clock controller there, then set the ProgressBar value back to 0.
void RunAnimation()
{
Duration duration = new Duration(TimeSpan.FromSeconds(10));
DoubleAnimation doubleanimation = new DoubleAnimation(100.0, duration);
doubleanimation.Completed += ProgressBarCompleted;
ProgBar.BeginAnimation(ProgressBar.ValueProperty, doubleanimation);
}
private void ProgressBarCompleted(object sender, EventArgs e)
{
var clock = (AnimationClock)sender;
clock.Controller.Remove();
ProgBar.Value = 0;
}
Note: ProgBar is defined in a .xaml file like
<ProgressBar Margin="0,0,0,0"
Padding="0,0,0,0"
x:Name="ProgBar"
Width="800"
HorizontalAlignment="Right"
Foreground="LightGray"/>

Storyboard.Completed Event Handler Preventing Code From Executing

I am trying to create a simulation program that animates based on the user's input. I am running into an error when I try and create an eventhandler for mystoryboard.completed event. I have read numerous different API articles and forum posts on eventhandling and storyboards but I can't seem to find the cause of my error.
My code compiles and the window displays but anything after the line where I set up the eventhandler doesn't get executed. My MainWindow where I set everything up is shown below.
public MainWindow()
{
InitializeComponent();
titleTextBlock.Text = "MainWindow()";
//this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
mainSystem = new BalanceSystem(3);
leftBlock = new SystemComponents.Block(0, 100, 150, 100, mainSystem);
rightBlock = new SystemComponents.Block(1, 100, 150, 100, mainSystem);
balanceBeam = new SystemComponents.Bar(0, 0, 250, 150, 100, mainSystem);
mainSystem.addComponent(leftBlock, leftWeight);
mainSystem.addComponent(rightBlock, rightWeight);
mainSystem.addComponent(balanceBeam, balanceBar);
titleTextBlock.Text = "LOADED";
}
The constructor for "BalanceSystem" is when things start to go wrong. It steps into the constructor shown below:
public BalanceSystem(int count)
{
componentCount = count;
masterTimeline = new MovementTimeline(1);
}
After entering the constructor for "BalanceSystem" it moves on to the constructor for my custome class "MovementTimeline". The line that breaks everything is the creation and subscription of the eventhandler for masterStoryboard.Completed.
class MovementTimeline
{
private Storyboard masterStoryboard;
private Duration systemDuration;
public MovementTimeline(int totalTime)
{
systemDuration = new Duration(TimeSpan.FromSeconds(totalTime));
masterStoryboard.Completed += new EventHandler(masterStoryboard_Completed);
}
void masterStoryboard_Completed(object sender, EventArgs e)
{
masterStoryboard.Children.Clear();
//masterStoryboard.Completed -= masterStoryboard_Completed;
}
}
Once the compiler or program hits the line where the new EventHandler is created it stops executing the rest of my code and loads the window as is. I cannot for the life of me figure out why this is happening.
it looks to me like you are adding an eventhandler without ever creating a StoryBoard object

How do I create an array of type ImageSource?

I am trying to make an array of pictures to set to my image control. There is a loop that will run through the pictures and display each for a second until it stops on a random picture.
My Approach: I stored the pictures paths in a string which I then put into an array. Inside my loop I create a new ImageSource and define it by the array item who's turn it is to be displayed. I believe that the reason my program won't work is that the ImageSource can't pull the image from the file and set it to the image controller fast enough before I sleep the thread to let the picture display for a second.
Here is my code (assume strings are correct paths and I tried countless Thread.Sleep() variables)
This code runs but it only displays the last picture instead of flipping through all of them rapidly then stopping on the last picture.
Would making the array of type ImageSource keep the pictures "on hand" to be displayed faster?
Can WPF support the speed required to get and set images to image controller?
Could my sleep thread be executing too fast before image is set and displayed?
private void ButtonClick(object sender, RoutedEventArgs e)
{
String[] picArray = new String[] { kristen, justin, brandon, sarah, miles, nina };
int i = 0;
x = rnd.Next(0,5);
while (i < rnd.Next(10,50))
{
ImageSource _currentPic = new BitmapImage(new Uri(picArray[x]));
cImage.Source = _currentPic;
if (x == 5)
x = 0;
else
x++;
Thread.Sleep(100);
i++;
}
}
The loop stops on a "random" point in the array to randomize whose picture the method ends on.
Thanks ahead of time.
If this were a normal windows form, you could have used
this.Refresh()
just before the Sleep() to refresh the screen (displaying the current image).
However, since it's WPF, you have to spawn off that loop into a separate thread (such as BackgroundWorker). Another option would be to set up a Timer and just call the contents of the loop every time the timer executes.
Regardless, you have to get that logic off into a new thread for WPF.
Your code is not creating any new threads. It's not WPF's fault that your UI will freeze.
Doing Thread.Sleep() in the UI Thread is simply going to make your application freeze, no matter if WPF or not.
Also, You don't need any of that in WPF. In WPF, There's DataBinding, which removes the need for all these horrible hacks of manipulating the UI in procedural code you might be used to if you are used to some random dinosaur useless framework such as winforms.
Here's the right way to do what you're describing in WPF:
<Window x:Class="MiscSamples.SlideShowSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SlideShowSample" Height="300" Width="300">
<Image Source="{Binding ImageSource}"/>
</Window>
Code Behind:
public partial class SlideShowSample : Window
{
public SlideShowSample()
{
InitializeComponent();
DataContext = new SlideShowViewModel();
}
}
ViewModel:
public class SlideShowViewModel:PropertyChangedBase
{
public const string SourceURL = "http://lorempixel.com/400/200/sports/";
private string _imageSource;
public string ImageSource
{
get { return _imageSource; }
set
{
_imageSource = value;
OnPropertyChanged("ImageSource");
}
}
private System.Threading.Timer _slideShowTimer;
private System.Random _random = new Random();
public SlideShowViewModel()
{
_slideShowTimer = new Timer(x => OnTimerTick(), null, 1000, 3000);
}
private void OnTimerTick()
{
ImageSource = SourceURL + _random.Next(1, 10).ToString();
}
}
PropertyChangedBase (MVVM Helper Class):
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
Application.Current.Dispatcher.BeginInvoke((Action) (() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
See how my code does not manipulate any UI element.
My sample downloads images from the web, therefore it should be much faster if you use locally stored images.
Notice how the UI Does NOT freeze at any time. There's still a little delay because the actual image data is being downloaded from the web. That download IS being performed in the UI thread, so you could actually improve my sample to download async, but since you are using locally stored images, it doesn't matter.

How to slow down animated gif

I have a WinForms app that displays an animated gif in the simplest possible way - there is a PictureBox that loads the .gif directly.
The code generated by the WinForms designer looks like this:
//
// pictureBoxHomer
//
this.pictureBoxHomer.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None;
this.pictureBoxHomer.Dock = System.Windows.Forms.DockStyle.Fill;
this.pictureBoxHomer.Image = ((System.Drawing.Image)(resources.GetObject("pictureBoxHomer.Image")));
this.pictureBoxHomer.Location = new System.Drawing.Point(3, 3);
this.pictureBoxHomer.Name = "pictureBoxHomer";
this.pictureBoxHomer.Size = new System.Drawing.Size(905, 321);
this.pictureBoxHomer.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage;
this.pictureBoxHomer.TabIndex = 0;
this.pictureBoxHomer.TabStop = false;
The image is, of course, this: http://media.tumblr.com/tumblr_m1di1xvwTe1qz97bf.gif
Problem: while this animated gif displays wondrously in the browser, it is running way too fast in the WinForms app, which is not as happy as needed. So:
Question: is there a way to slow down an animated gif in a WinForms app?
I believe the answer is rather image-related than C#. If you edit that specific image in a tool like GIMP and take a look at the layers, you'll see it's a composition of 10 layers (frames) but no "delay time" between them is set - it has (0ms) in layer's attribute. You can edit layer's attribute and change it by right-clicking on it and selecting that option in menu. Of course, at the end you have to export your new image and save it as a GIF, selecting "animated" in options.
I believe in this case (when no delay time between frames is specified) web browser and C# PicutureBox force their own,different, default values. So, if you put a delay let say 100ms, like described here in step 3, you'll make the animation slow down.
For future reference, it is possible to override the delay time of a GIF in a picture box. Here is a rough example:
public partial class Form1 : Form
{
private FrameDimension dimension;
private int frameCount;
private int indexToPaint;
private Timer timer = new Timer();
public Form1()
{
InitializeComponent();
dimension = new FrameDimension(this.pictureBox1.Image.FrameDimensionsList[0]);
frameCount = this.pictureBox1.Image.GetFrameCount(dimension);
this.pictureBox1.Paint += new PaintEventHandler(pictureBox1_Paint);
timer.Interval = 100;
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
indexToPaint++;
if(indexToPaint >= frameCount)
{
indexToPaint = 0;
}
}
void pictureBox1_Paint(object sender, PaintEventArgs e)
{
this.pictureBox1.Image.SelectActiveFrame(dimension, indexToPaint);
e.Graphics.DrawImage(this.pictureBox1.Image, Point.Empty);
}
}

Categories