I've been trying to get a WebBrowser to draw to a texture in XNA 4.0, and I've found several guides on how to do it. The problem is when I try to implement it, whether I change the Url property or call Navigate() it just won't load the page. I have a feeling I'm being a bit ignorant about the threading required, since my project is not started as an STA thread, so I create a separate thread to start the web browser and render to a bitmap.
Here's how I start it:
public void LoadTexture(GraphicsDevice gfx, ContentManager content, string filename, float duration = -1f)
{
this.gfx = gfx;
this.filename = filename;
this.duration = duration;
_resetEvent = new AutoResetEvent(false);
Thread thread = new Thread(GetWebScreenshot);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
_resetEvent.WaitOne();
}
And here's GetWebScreenshot:
public void GetWebScreenshot()
{
this.web = new WebBrowser();
this.web.Size = new Size(gfx.Viewport.Width, gfx.Viewport.Height);
this.web.Url = new Uri(this.filename);
while (this.web.ReadyState != WebBrowserReadyState.Complete)
{
if (this.web.ReadyState != WebBrowserReadyState.Uninitialized)
{
Console.WriteLine(this.web.ReadyState.ToString());
}
}
bitmap = new Bitmap(this.gfx.Viewport.Width, this.gfx.Viewport.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
this.web.DrawToBitmap(bitmap, new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height));
this.texture = BitmapToTexture2D(this.gfx, bitmap);
_resetEvent.Set();
}
The ReadyState property never changes from Uninitialized, I've also tried using the DocumentReady event, and that never gets fired. I've also tried Join() instead of AutoResetEvent, but nothing seems to work.
I was right, it was ignorance on my part. The critical thing about ActiveX controls and Single Threaded Apartments is that the message queue needs to be pumped. So now I've restructured my code to the following:
public void LoadTexture(GraphicsDevice gfx, ContentManager content, string filename, float duration = -1f)
{
this.gfx = gfx;
this.filename = filename;
this.duration = duration;
Thread thread = new Thread(GetWebScreenshot);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
}
public void GetWebScreenshot()
{
this.web = new WebBrowser();
this.web.Size = new Size(gfx.Viewport.Width, gfx.Viewport.Height);
this.web.Url = new Uri(this.filename);
this.web.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(web_DocumentCompleted);
Application.Run(); // Starts pumping the message queue (and keeps the thread running)
}
void web_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
Bitmap bitmap = new Bitmap(this.gfx.Viewport.Width, this.gfx.Viewport.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
this.web.DrawToBitmap(bitmap, new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height));
this.texture = HTMLTextureFactoryMachine.BitmapToTexture2D(this.gfx, bitmap);
Application.ExitThread(); // Exits the thread
}
This works no problem.
Related
I'm using the .NET WebBrowser in order to render an html page and save that as a bitmap (got the code from another stackoverflow post). The problem is that the thread that actually starts the web browser never closes.
The thread should close when the StartBrowser Method finishes but it never does because Application.Run never ends.
My question is : how do I close the application after the WebBrowser finishes rendering the page and saves the bitmap ? Adding Application.Exit();
inside the DocumentCompleted event doesn't seem to work.
Code:
public class DotNetRenderer : IHtmlRenderer
{
static Bitmap bmp;
Thread th;
public void initializeRenderer()
{
}
public Bitmap renderHtml(string htmlContent)
{
th = new Thread(() => StartBrowser(htmlContent));
th.SetApartmentState(ApartmentState.STA);
th.Start();
th.Join();
return bmp;
}
public static void StartBrowser(string source)
{
var webBrowser = new WebBrowser();
webBrowser.ScrollBarsEnabled = false;
webBrowser.DocumentCompleted +=
webBrowser_DocumentCompleted;
webBrowser.DocumentText = source;
Application.Run();
}
static void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
var webBrowser = (WebBrowser)sender;
bmp = new Bitmap(webBrowser.Width, webBrowser.Height);
webBrowser.DrawToBitmap(bmp, new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height));
Application.ExitThread();
}
}
Application.ExitThread() does what I want, but now I don't know why the webBrowser Width and Height are always (250,250) regardless of the input HTML.
my goal here is to play some animation and make the UI wait until it is finished to execute some other code. I have a class like this:
class Game
{
private string currentClickedCommand;
private string currentClickedSlot;
private GamePage gamePage;// gamePage na kojem se vrsi radnja
private string[] chosenCommands;// ovo je string array u kojem su sadrzana imena odabranih komandi
private int chapterNumber;
// Kontekst stranice koristen za update objekata UI
// Za kontrolu animacija
public static bool isAnimationComplete;
private BitmapImage currentImage;
public Game(GamePage gp, int chapterNum)
{
currentClickedCommand = null;
currentClickedSlot = null;
gamePage = gp;
chapterNumber = chapterNum;
isAnimationComplete = false;
chosenCommands = new string[5];
gamePage.characterRectangle.Visibility = System.Windows.Visibility.Collapsed;
Thread AnimationThread = new Thread(playIntroAnimation);
AnimationThread.Start();
while (!isAnimationComplete) { }
loadCommands();
loadSlots();
}
public void playIntroAnimation() {
// Pretpostavljam da na osnovu baze znam koja je animacija za ovaj chapter navedena kao uvodna
Debug.WriteLine("on other thread playing animation");
// Sakrivam gridove komandi i ostalog
Deployment.Current.Dispatcher.BeginInvoke(new Action(() =>
{
gamePage.CommandGrid.Visibility = System.Windows.Visibility.Collapsed;
gamePage.SlotGrid.Visibility = System.Windows.Visibility.Collapsed;
MessageBox.Show("Prvi dispatch");
Debug.WriteLine("prvi dispatch");
}));
// Ucitavam pozadinsku sliku preko canvasa cijelog
Deployment.Current.Dispatcher.BeginInvoke(new Action(() =>
{
ImageBrush bgImage = new ImageBrush();
Uri imgSource = new Uri(#"Assets/Chapter backgrounds/chapter_1_introduction.png", UriKind.Relative);
BitmapImage img = new BitmapImage(imgSource);
bgImage.ImageSource = img;
gamePage.mainCanvas.Background = bgImage;
MessageBox.Show("Drugi dispatch");
}));
// Kreiram parametre animacije
Deployment.Current.Dispatcher.BeginInvoke(new Action(() =>
{
Uri sheetSource = new Uri(#"Assets/Sheets/test_wave_sheet.png", UriKind.Relative);
currentImage = new BitmapImage(sheetSource);
currentImage.ImageOpened += new EventHandler<RoutedEventArgs>(setAnimationParams);
Image i = new Image();
i.Source = currentImage;
MessageBox.Show("Treci dispatch");
}));
// Odredjujem lika i animiram ga -- visinu i sirinu znam iz baze(kad dobavim sheet i ostalo imam gotove parametere
// sada moram hardkodirat radi testa
Deployment.Current.Dispatcher.BeginInvoke(new Action(() =>
{
gamePage.characterRectangle.Visibility = System.Windows.Visibility.Visible;
Canvas.SetLeft(gamePage.characterRectangle, gamePage.mainCanvas.ActualWidth / 4.0);
Canvas.SetTop(gamePage.characterRectangle, gamePage.mainCanvas.ActualHeight / 5.0);
MessageBox.Show("Cetvrti dispatch");
}));
}
private void setAnimationParams(object sender, RoutedEventArgs e)
{
Deployment.Current.Dispatcher.BeginInvoke(() => {
AnimationClasses.AnimationParams parameters = new AnimationClasses.AnimationParams(currentImage, 462, 438, 7, true, currentImage.PixelWidth, false, 2);
new AnimationClasses.Animation().Animate(gamePage.characterRectangle, parameters);
});
}
As you can see, I've been trying to do this using Deployment.Current... etc. but no luck. I have been debugging but it simply never gets into executing this code and nothing is happening.
I create an instance of Game on Page like this:
public GamePage()
{
InitializeComponent();
game = new Game(this, Convert.ToInt32(PhoneApplicationService.Current.State["chapter"]));
}
I have a static attribute(isAnimationComplete) which is set elsewhere to true, when the animation is complete. The 2 commands after(loadCommands, loadSlots) need to wait for this to finish and then execute.
Thank you!
You simply have a deadlock.
The UI thread is calling the constructor of the Game class. Inside, you're starting a background thread, then waiting in a loop:
while (!isAnimationComplete) { }
Which means that the UI thread won't be available for other operations until you set the isAnimationComplete flag to true. Which will never happen since you're doing that when the animations are done executing... Animations that are waiting for the UI thread to be free.
I have a method which is a time consuming one and therefore I have been trying to implement a BackgroundWorker, but it does not allow accessing UI controls which I have read and tried (hacking it) but to no avail.
What my method does: Creates a new BitmapImage, sets the source local or streamed (the parameter), writes it to a new WriteableBitmap, which is used for ConvertToGrayscale and then saves the BW Copy to IsolatedStorage in a folder.
So all this happens quite fast. But, only when I have say less than 25 Source Images. If I have about 100+ Images, this takes considerably long like 20 seconds or more and therefore, I would like to show a ProgressBar in the same PhoneApplicationPage but I have been struggling with how to not block the UI and show the ProgressBar while the method is doing its work.
This is the code that I have:
void GetImages()
{
if (!myIsolatedStorage.DirectoryExists("ImagesBW") && !_appsettings.Contains("_update"))
{
myIsolatedStorage.CreateDirectory("ImagesBW ");
for (int i = 0; i < coll.Desserts.Count; i++)
{
BitmapImage bmp = new BitmapImage();
bmp.CreateOptions = BitmapCreateOptions.None;
if (coll.Desserts[i].HasAssociatedImage)
{
bmp.SetSource(coll.Desserts[i].GetImage());
WriteableBitmap wb = new WriteableBitmap(bmp);
ConvertToGrayscale(wb);
BitmapImage bit = ConvertWBtoBI(wb);
SaveBWCopy(bi, i.ToString());
}
else
{
bmp.UriSource = new Uri("/Assets/Images/MissingArt.png", UriKind.Relative);
WriteableBitmap wb = new WriteableBitmap(bmp);
ConvertToGrayscale(wb);
BitmapImage bit = ConvertWBtoBI(wb);
SaveBWCopy(bi, i.ToString());
}
}
_appsettings["_firstLaunch"] = "false";
_appsettings.Save();
}
else if (myIsolatedStorage.DirectoryExists("ImagesBW ") && _appsettings.Contains("_update"))
{
string[] files = myIsolatedStorage.GetFileNames("ImagesBW/*");
for (int s = 0; s < files.Length; s++)
{
myIsolatedStorage.DeleteFile("ImagesBW/" + s + ".jpg");
}
myIsolatedStorage.DeleteDirectory("ImagesBW");
myIsolatedStorage.CreateDirectory("ImagesBW");
for (int i = 0; i < coll.Desserts.Count; i++)
{
BitmapImage bmp = new BitmapImage();
bmp.CreateOptions = BitmapCreateOptions.None;
if (coll.Desserts[i].HasAssociatedImage)
{
bmp.SetSource(coll.Desserts[i].GetImage());
WriteableBitmap wb = new WriteableBitmap(bmp);
ConvertToGrayscale(wb);
BitmapImage bit = ConvertWBtoBI(wb);
SaveBWCopy(bi, i.ToString());
}
else
{
bmp.UriSource = new Uri("/Assets/Images/MissingArt.png", UriKind.Relative);
WriteableBitmap wb = new WriteableBitmap(bmp);
ConvertToGrayscale(wb);
BitmapImage bit = ConvertWBtoBI(wb);
SaveBWCopy(bi, i.ToString());
}
}
_appsettings.Remove("_update");
_appsettings.Save();
}
btnStart.IsEnabled = true;
}
Backgroundorker is your best bet. There are huge amount of resources on the web how to implement it. But the idea behind is that Backgroundworker runs in a separate thread from the UI. You can access main thread UI by two methods, a delegate or through the Backgroundworker's ProgressChanged method, which gives access to you UI thread.
Lets say you have this.
public MainWindow()
{
InitializeComponent();
//if you want to disable you button you can do it here
BackgroundWorker _bw = new BackgroundWorker();
_bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
_bw.WorkerReportsProgress = true;
_bw.ProgressChanged += new ProgressChangedEventHandler(_bw_ProgressChanged);
_bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bw_RunWorkerCompleted);
_bw.RunWorkerAsync();
//or here
//Display progress bar here too
}
void _bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//this can give you access to the UI after work is completed
// to check that everything is ok or hide progress bar
}
void _bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//this will give you access to the UI in the middle of you work like update progress bar
}
void _bw_DoWork(object sender, DoWorkEventArgs e)
{
//actual work here including GetImages
//work work work
GetImages();
}
In you getimages method after the SaveBWCopy you can add this to update the progress bar
_bw.ReportProgress(int progress )
//progress is the percentage you want to send to progress bar to display that is going to be in the e eventargument you passed.
I have button on my window. After user clickes the button i want my application to animate loading label (with rotationg it), during the other thread gets some data from database. After loading data from DB animation must end. The task seems simple , but it doesn't work.
The problem is that animation whatever i do animation starts only after loading from the database when it is not needed.
Help please. Here some code:
private void LoginButtonClick(object sender, RoutedEventArgs e)
{
Thread thread = new Thread(new ThreadStart(
delegate()
{
DispatcherOperation dispatcherOp =
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(
delegate()
{
var da = new DoubleAnimation(360, 0, new Duration(TimeSpan.FromSeconds(1)));
var rt = new RotateTransform();
loadingLabel.RenderTransform = rt;
loadingLabel.RenderTransformOrigin = new Point(0.5, 0.5);
da.RepeatBehavior = RepeatBehavior.Forever;
rt.BeginAnimation(RotateTransform.AngleProperty, da);
}));
dispatcherOp.Completed += new EventHandler(DispatcherOpCompleted);
}));
thread.Start();
}
void DispatcherOpCompleted(object sender, EventArgs e)
{
//Loading From Database
}
The Dispatcher.Completed event is executed on the main UI thread. Your worker thread is just queueing the dispatcher operation and exiting. Instead of creating a thread that starts the animation and then doing your database loading in the Completed handler, just start your animation in the main thread, and then create a worker thread to do the database loading.
private void LoginButtonClick(object sender, RoutedEventArgs e)
{
var da = new DoubleAnimation(360, 0, new Duration(TimeSpan.FromSeconds(1)));
var rt = new RotateTransform();
loadingLabel.RenderTransform = rt;
loadingLabel.RenderTransformOrigin = new Point(0.5, 0.5);
da.RepeatBehavior = RepeatBehavior.Forever;
rt.BeginAnimation(RotateTransform.AngleProperty, da);
Thread thread = new Thread(new ThreadStart(LoadData));
thread.Start();
}
void LoadData()
{
//Loading From Database
// Use a Dispatch.BeginInvoke here to stop the animation
// and do any other UI updates that use the results of the database load
}
What's the best way to asynchronously load an BitmapImage in C# using WPF?
I was just looking into this and had to throw in my two cents, though a few years after the original post (just in case any one else comes looking for this same thing I was looking into).
I have an Image control that needs to have it's image loaded in the background using a Stream, and then displayed.
The problem that I kept running into is that the BitmapSource, it's Stream source and the Image control all had to be on the same thread.
In this case, using a Binding and setting it's IsAsynch = true will throw a cross thread exception.
A BackgroundWorker is great for WinForms, and you can use this in WPF, but I prefer to avoid using the WinForm assemblies in WPF (bloating of a project is not recommended, and it's a good rule of thumb too). This should throw an invalid cross reference exception in this case too, but I didn't test it.
Turns out that one line of code will make any of these work:
//Create the image control
Image img = new Image {HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, VerticalAlignment = System.Windows.VerticalAlignment.Stretch};
//Create a seperate thread to load the image
ThreadStart thread = delegate
{
//Load the image in a seperate thread
BitmapImage bmpImage = new BitmapImage();
MemoryStream ms = new MemoryStream();
//A custom class that reads the bytes of off the HD and shoves them into the MemoryStream. You could just replace the MemoryStream with something like this: FileStream fs = File.Open(#"C:\ImageFileName.jpg", FileMode.Open);
MediaCoder.MediaDecoder.DecodeMediaWithStream(ImageItem, true, ms);
bmpImage.BeginInit();
bmpImage.StreamSource = ms;
bmpImage.EndInit();
//**THIS LINE locks the BitmapImage so that it can be transported across threads!!
bmpImage.Freeze();
//Call the UI thread using the Dispatcher to update the Image control
Dispatcher.BeginInvoke(new ThreadStart(delegate
{
img.Source = bmpImage;
img.Unloaded += delegate
{
ms.Close();
ms.Dispose();
};
grdImageContainer.Children.Add(img);
}));
};
//Start previously mentioned thread...
new Thread(thread).Start();
Assuming you're using data binding, setting Binding.IsAsync property to True seems to be a standard way to achieve this.
If you're loading the bitmap in the code-behind file using background thread + Dispatcher object is a common way to update UI asynchronous
This will allow you to create the BitmapImage on the UI thread by using the HttpClient to do the async downloading:
private async Task<BitmapImage> LoadImage(string url)
{
HttpClient client = new HttpClient();
try
{
BitmapImage img = new BitmapImage();
img.CacheOption = BitmapCacheOption.OnLoad;
img.BeginInit();
img.StreamSource = await client.GetStreamAsync(url);
img.EndInit();
return img;
}
catch (HttpRequestException)
{
// the download failed, log error
return null;
}
}
To elaborate onto aku's answer, here is a small example as to where to set the IsAsync:
ItemsSource="{Binding IsAsync=True,Source={StaticResource ACollection},Path=AnObjectInCollection}"
That's what you would do in XAML.
BitmapCacheOption.OnLoad
var bmp = await System.Threading.Tasks.Task.Run(() =>
{
BitmapImage img = new BitmapImage();
img.BeginInit();
img.CacheOption = BitmapCacheOption.OnLoad;
img.UriSource = new Uri(path);
img.EndInit();
ImageBrush brush = new ImageBrush(img);
}
Use or extend System.ComponentModel.BackgroundWorker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Personally, I find this to be the easiest way to perform asynchronous operations in client apps. (I've used this in WinForms, but not WPF. I'm assuming this will work in WPF as well.)
I usually extend Backgroundworker, but you dont' have to.
public class ResizeFolderBackgroundWorker : BackgroundWorker
{
public ResizeFolderBackgroundWorker(string sourceFolder, int resizeTo)
{
this.sourceFolder = sourceFolder;
this.destinationFolder = destinationFolder;
this.resizeTo = resizeTo;
this.WorkerReportsProgress = true;
this.DoWork += new DoWorkEventHandler(ResizeFolderBackgroundWorker_DoWork);
}
void ResizeFolderBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
DirectoryInfo dirInfo = new DirectoryInfo(sourceFolder);
FileInfo[] files = dirInfo.GetFiles("*.jpg");
foreach (FileInfo fileInfo in files)
{
/* iterate over each file and resizing it */
}
}
}
This is how you would use it in your form:
//handle a button click to start lengthy operation
private void resizeImageButtonClick(object sender, EventArgs e)
{
string sourceFolder = getSourceFolderSomehow();
resizer = new ResizeFolderBackgroundWorker(sourceFolder,290);
resizer.ProgressChanged += new progressChangedEventHandler(genericProgressChanged);
resizer.RunWorkerCompleted += new RunWorkerCompletedEventHandler(genericRunWorkerCompleted);
progressBar1.Value = 0;
progressBar1.Visible = true;
resizer.RunWorkerAsync();
}
void genericRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Visible = false;
//signal to user that operation has completed
}
void genericProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
//I just update a progress bar
}