I have a background agent I created to update my live tile. The agent schedules and executes fine, but the code that the agent executes has became the problem - it simply fails to execute fully and provides no error. From what I can tell, there are no restricted APIs that I am using, unless the Cimbalino toolkit is somehow providing a problem, even though I am using the background-agent-specific version from NuGet.
Code appears to stop executing when RenderText() and RenderTextWide() fail to run. No error is provided. The same code works perfectly fine when run in the foreground app.
using System;
using System.Windows;
using Microsoft.Phone.Scheduler;
using Microsoft.Phone.Shell;
using Microsoft.Phone.Info;
using Cimbalino.Phone.Toolkit;
using System.Linq;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.Windows.Controls;
using System.IO.IsolatedStorage;
using Cimbalino.Phone.Toolkit.Extensions;
namespace ScheduledTaskAgent1
{
public class ScheduledAgent : ScheduledTaskAgent
{
private static volatile bool _classInitialized;
public ScheduledAgent()
{
if (!_classInitialized)
{
_classInitialized = true;
// Subscribe to the managed exception handler
Deployment.Current.Dispatcher.BeginInvoke(delegate
{
Application.Current.UnhandledException += ScheduledAgent_UnhandledException;
});
}
}
/// Code to execute on Unhandled Exceptions
private void ScheduledAgent_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
if (System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Break();
}
}
//Method to create normal tile image
private static void RenderText(string currproperty)
{
WriteableBitmap b = new WriteableBitmap(336, 336);
var canvas = new Grid();
canvas.Width = b.PixelWidth;
canvas.Height = b.PixelHeight;
var background = new Canvas();
background.Height = b.PixelHeight;
background.Width = b.PixelWidth;
SolidColorBrush backColor = new SolidColorBrush(Colors.Transparent);
background.Background = backColor;
TextBlock currtemp = new TextBlock();
currtemp.FontSize = 100;
currtemp.FontFamily = new FontFamily("Segoe UI Light");
currtemp.FontWeight = FontWeights.Bold;
currtemp.Foreground = new SolidColorBrush(Colors.White);
currtemp.Text = currproperty;
currtemp.Margin = new Thickness(20, 10, 0, 0);
currtemp.Width = b.PixelWidth - currtemp.Margin.Left * 2;
canvas.Children.Add(currtemp);
b.Render(background, null);
b.Render(canvas, null);
b.Invalidate();
using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream imageStream = new IsolatedStorageFileStream("/Shared/ShellContent/BackBackgroundImage2.png", System.IO.FileMode.Create, isf))
{
b.SavePng(imageStream);
}
}
}
//Method to create wide tile image
private static void RenderTextWide(string currproperty)
{
WriteableBitmap b = new WriteableBitmap(691, 336);
var canvas = new Grid();
canvas.Width = b.PixelWidth;
canvas.Height = b.PixelHeight;
var background = new Canvas();
background.Height = b.PixelHeight;
background.Width = b.PixelWidth;
//Created background color as Accent color
SolidColorBrush backColor = new SolidColorBrush(Colors.Transparent);
background.Background = backColor;
TextBlock currtemp = new TextBlock();
currtemp.FontSize = 100;
currtemp.FontFamily = new FontFamily("Segoe UI Light");
currtemp.FontWeight = FontWeights.Bold;
currtemp.Foreground = new SolidColorBrush(Colors.White);
currtemp.Text = currproperty;
currtemp.Margin = new Thickness(20, 10, 0, 0);
currtemp.Width = b.PixelWidth - currtemp.Margin.Left * 2;
canvas.Children.Add(currtemp);
using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream imageStream = new IsolatedStorageFileStream("/Shared/ShellContent/WideBackBackgroundImage2.png", System.IO.FileMode.Create, isf))
{
b.SavePng(imageStream);
}
}
}
//When task invokes, run the tile update code.
protected override void OnInvoke(ScheduledTask task)
{
ShellTile tile = ShellTile.ActiveTiles.FirstOrDefault();
if (tile != null)
{
FlipTileData flipTile = new FlipTileData();
flipTile.Title = "";
flipTile.BackTitle = "Atmosphere";
RenderText("Test");
RenderTextWide("Test");
flipTile.BackBackgroundImage = new Uri("/Assets/NewUI/1.PNG", UriKind.Relative); //Default image for Background Image Medium Tile 336x336 px
flipTile.BackgroundImage = new Uri(#"isostore:/Shared/ShellContent/BackBackgroundImage2.png", UriKind.Absolute); //Generated image for Back Background 336x336
flipTile.WideBackBackgroundImage = new Uri("/Assets/NewUI/2.PNG", UriKind.Relative); ////Default image for Background Image Wide Tile 691x336 px
flipTile.WideBackgroundImage = new Uri(#"isostore:/Shared/ShellContent/WideBackBackgroundImage2.png", UriKind.Absolute);
tile.Update(flipTile);
//Tile updated, tell agent operation complete
NotifyComplete();
}
}
}
}
I just ran the code that you shared in a background agent with debugger attached, and the problem was quite obvious - it throws an exception! An invalid cross-thread access exception to be precise. All UI-related work must happen int the UI thread and instantiating controls and WriteableBitmaps is such work.
Solution
You need to use Deployment.Current.Dispatcher.BeginInvoke to invoke RenderText and RenderTextWide in the appropriate thread. You can also just invoke everything in OnInvoke in the UI thread.
Note: Deployment.Current.Dispatcher.BeginInvoke invokes the given action asynchronously, so if you don't run everything on the UI thread, you may need to do something to sync the different threads.
NotifyComplete
You must ALWAYS call NotifyComplete AFTER you've done all the work. Basically what you need to do is try-catch(all) and then call NotifyComplete. Also, you may want to put it in ScheduledAgent_UnhandledException just in case.
NotifyComplete shouldn't be in an if as in your code, although that condition is always true in Windows Phone. ShellTile.ActiveTiles always has at least one tile - the primary tile - even if it's not pinned.
Debugging background agents
Yes, you can do that. If the agent is not working, the best way to see what's the problem is to launch the app with the debugger attached, force the background agent's invocation and put a breakpoint in the code that you want to check. Use ScheduledActionService.LaunchForTest to start the background agent sooner than normal.
Offtopic/Questions
May I ask what are you trying to achieve with that private static volatile bool _classInitialized field? And why is it volatile?
I also have seen this problem with my application after upgrading from WP8.0 to WP8.1 Silverlight. My scheduled agent "crashes" absolutely silently at arbitrary points in my code (generating live tiles). The crashes or hangs occur frequently and no exceptions are thrown. I've been working on this on and off for over a month now with no success. I've been very side-tracked thinking I was causing the problem myself somehow. I did find a reference last night that may be the solution. I haven't fully tested it yet in my application.
In the WP8.1 Silverlight API change notes there is a very cryptic paragraph that says:
For a managed Windows Phone 8 ScheduledTaskAgent to be able to access
Silverlight 8.1 features, it runs on the modern execution stack, which
is converged with Windows. This uses a different CPU quota mechanism
than Windows Phone 8. In some cases, a Silverlight 8.1 background
agent might find that it gets less CPU time than it did previously. If
that happens, the app may choose to call RequestAccessAsync(). This
will increase the CPU quota given to the agent.
I've found some posts elsewhere indicating that the following code fixes the problem for some when placed immediately before adding a background agent task:
await Windows.ApplicationModel.Background.BackgroundExecutionManager.RequestAccessAsync();
I would love to hear if others are seeing these types of problems and if this helps them.
SOLVED!.
I have the same problem and I solved with the following code:
Deployment.Current.Dispatcher.BeginInvoke(delegate() { RenderText("Test"); });
Deployment.Current.Dispatcher.BeginInvoke(delegate() { RenderTextWide("Test"); });
The file ScheduledAgent.cs:
protected override void OnInvoke(ScheduledTask task)
{
ShellTile tile = ShellTile.ActiveTiles.FirstOrDefault();
if (tile != null)
{
FlipTileData flipTile = new FlipTileData();
flipTile.Title = "";
flipTile.BackTitle = "Atmosphere";
Deployment.Current.Dispatcher.BeginInvoke(delegate() { RenderText("Test"); });
Deployment.Current.Dispatcher.BeginInvoke(delegate() { RenderTextWide("Test"); });
flipTile.BackBackgroundImage = new Uri("/Assets/NewUI/1.PNG", UriKind.Relative); //Default image for Background Image Medium Tile 336x336 px
flipTile.BackgroundImage = new Uri(#"isostore:/Shared/ShellContent/BackBackgroundImage2.png", UriKind.Absolute); //Generated image for Back Background 336x336
flipTile.WideBackBackgroundImage = new Uri("/Assets/NewUI/2.PNG", UriKind.Relative); ////Default image for Background Image Wide Tile 691x336 px
flipTile.WideBackgroundImage = new Uri(#"isostore:/Shared/ShellContent/WideBackBackgroundImage2.png", UriKind.Absolute);
tile.Update(flipTile);
//Tile updated, tell agent operation complete
NotifyComplete();
}
}
Related
Hi so like the title says, i'm trying to make an image Viewer for a project ( a bit in the kind of Windows Image viewer). I saw a lot of code displaying their RenderForm into a RenderLoop, but I don't like this solution since I don't want to refresh the image infinitely in the RenderLoop. I want to call the Draw method only when I need to redraw(on a zoom by example.) The problem is, now I've tried to use renderLoop.Show() but it does not stay on the screen and close right after all code has been executed...
using SharpDX;
using SharpDX.Windows;
using SharpDX.D3DCompiler;
using SharpDX.Direct2D1;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Mathematics;
using System.Drawing;
using System.Windows.Forms;
using Device = SharpDX.Direct3D11.Device;
using Color = SharpDX.Color;
namespace SharpDXWic
{
public class SharpDXDisplay : IDisposable
{
private const int WIDTH = 1500;
private const int HEIGHT = 800;
private Device device;
private SwapChain swapChain;
private RenderForm renderForm;
private RenderTargetView targetView;
public SharpDXDisplay(string display_title)
{
renderForm = new RenderForm(display_title);
renderForm.Width = WIDTH;
renderForm.Height = HEIGHT;
Texture2D target;
SwapChainDescription scd = new SwapChainDescription()
{
BufferCount = 1,
Flags = SwapChainFlags.None,
IsWindowed = true,
ModeDescription = new ModeDescription(WIDTH,HEIGHT, new Rational(60, 1),Format.R8G8B8A8_UNorm),
OutputHandle = renderForm.Handle,
SampleDescription = new SampleDescription(1, 0),
SwapEffect = SwapEffect.Discard,
Usage = Usage.RenderTargetOutput
};
Device.CreateWithSwapChain( SharpDX.Direct3D.DriverType.Hardware,DeviceCreationFlags.Debug, scd, out device, out swapChain);
target = Texture2D.FromSwapChain<Texture2D>(swapChain, 0);
targetView = new RenderTargetView(device, target);
device.ImmediateContext.OutputMerger.SetRenderTargets(targetView);
renderForm.Show();
device.ImmediateContext.ClearRenderTargetView(targetView, Color.CornflowerBlue);
swapChain.Present(0, PresentFlags.None);
}
private void OnClosing()
{
Dispose();
}
public void Dispose()
{
device.Dispose();
swapChain.Dispose();
renderForm.Dispose();
targetView.Dispose();
}
}
}
My goal here would be to Draw() a form without entering the RenderLoop. I only want to refresh the image on-demand and not refresh it constantly.
It is in fact possible to display an image into a Windows Form without using an infinite loop. Adding action like zooming or resizing to your form and perform action (such has redraw your image) works perfectly.
Of course, if you are making a video game, that is not the best solution because you want constant render of your graphics. However, for an image viewer, it is perfectly fine to draw everything to a windows form only once and update it on ActionEvents.
I am using a BackgroundWorker to pull video from a camera and write it to a PictureBox on my WinForms form. In the BW thread I simply pull a frame from the camera, put it into the PictureBox, sleep, and continue:
while (CaptureThreadRunning)
{
Thread.Sleep(5);
Image tmp = Camera.GetFrame(500);
pbCameraFeed.Image = tmp;
//this.BeginInvoke(new MethodInvoker(delegate { pbCameraFeed.Image = Camera.GetFrame(500); }));
}
The issue is that eventually adjusting or moving the form around my screen will throw the exception System.InvalidOperationException with the message Additional information: Object is currently in use elsewhere. on the line pbCameraFeed.Image = tmp;
I assume that the library is trying to paint something to do with the PictureBox at the same time as my while loop is, so I switched to the this.BeginInvoke implementation that is commented out above. Unfortunately that cuts my framerate significantly. I am running this code on a very slow Mini PC which may be contributing to the issue.
What I really want is a way to update my GUI with the image reliably that doesn't drop my framerate by nearly half. Are there other standard ways to do this? A BW thread seemed perfect for this application, but am I missing something?
Thanks
If I were you I would definitely check out the AForge.NET Framework. No need to reinvent the wheel ;)
http://www.aforgenet.com/framework/samples/video.html
AForge.NET is an open source C# framework designed for developers and
researchers in the fields of Computer Vision and Artificial
Intelligence - image processing, neural networks, genetic algorithms,
fuzzy logic, machine learning, robotics, etc.
The framework is comprised by the set of libraries and sample
applications, which demonstrate their features:
AForge.Imaging - library with image processing routines and filters;
AForge.Vision - computer vision library;
AForge.Video - set of libraries for video processing;
...
I would recommend not to use a PictureBox, and instead directly draw to a UserControl surface. This can be easily done by addig code to the Paint and Invalidate events of a UserControl.
This example below, creates a user control which has a BitMap property that it's drawed to its surface every time the control is invalidated.
So, for example, to randomly render JPG images from folder D:\MyPictures, you can do the following:
using System.Windows.Forms;
using System.Drawing;
void Main()
{
var pictures = Directory.GetFiles(#"D:\MyPictures", "*.jpg");
var rnd = new Random();
var form = new Form();
var control = new MyImageControl() { Dock = DockStyle.Fill };
form.Controls.Add(control);
var timer = new System.Windows.Forms.Timer();
timer.Interval = 50;
timer.Tick += (sender, args) =>
{
if (control.BitMap != null)
control.BitMap.Dispose();
control.BitMap = new Bitmap(pictures[rnd.Next(0, pictures.Length)]);
control.Invalidate();
};
timer.Enabled = true;
form.ShowDialog();
}
public class MyImageControl : UserControl // or PictureBox
{
public Bitmap BitMap { get; set; }
public MyImageControl()
{
this.Paint += Graph_Paint;
this.Resize += Graph_Resize;
}
private void Graph_Paint(object sender, PaintEventArgs e)
{
if (this.BitMap != null)
{
lock (this.BitMap)
{
Graphics g = e.Graphics;
g.DrawImage(this.BitMap, this.ClientRectangle);
}
}
}
private void Graph_Resize(object sender, System.EventArgs e)
{
this.Invalidate();
}
}
I think this can be easily changed to render the camera images instead of the picture files.
The code was tested on LinqPad
we need to create WPF controls (say, a canvas with some Images on it) in a background thread and then take a screenshot of them. The controls must not be displayed.
I managed to create the control on a thread by making it an STA thread. Then I used the code from here http://blogs.msdn.com/b/swick/archive/2007/12/02/rendering-ink-and-image-to-a-bitmap-using-wpf.aspx to create the screenshot.
This doesn't work though: the control always has a size of 0 and therefore this crashes. Even if I specify a width and height manually it won't work, the saved image is always black.
Here's my code:
private void CreateScreenshotThread()
{
var image = CreateImage();
TakeScreenshot(image , #"e:\1.bmp");
}
I also tried to UpdateLayout() but without success. Do you have any idea how I can enforce a layout update and rendering of the control? I played around with PresentationSource but without success (don't fully understand the purpose of that class).
It is possible, the bit you are missing is possibly the measure and arrange of the control:
public MainWindow()
{
InitializeComponent();
var thread = new Thread(CreateScreenshot);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
private void CreateScreenshot()
{
Canvas c = new Canvas { Width = 100, Height = 100 };
c.Children.Add(new Rectangle { Height = 100, Width = 100, Fill = new SolidColorBrush(Colors.Red) });
var bitmap = new RenderTargetBitmap((int)c.Width, (int)c.Height, 120, 120, PixelFormats.Default);
c.Measure(new Size((int)c.Width, (int)c.Height));
c.Arrange(new Rect(new Size((int)c.ActualWidth, (int)c.ActualHeight)));
bitmap.Render(c);
var png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(bitmap));
using (Stream stm = File.Create("c:\\temp\\test.png"))
{
png.Save(stm);
}
}
Has anyone got any idea why I'm getting an OutOfMemoryException when I'm creating my custom tile?
I'm trying to create custom images for my primary tile on a windows phone 8 app from a ScheduledAgent. The error doesn't occur until my very last line of code is executed which is the NotifyComplete().
Here is the code (Not the cleanest but ok for prototyping I guess). This code only handles the wide tile and it tries to load an image an image downloaded from a website and then it tries to render a logo and a description over this image.
Here is the code:
private void CreateTiles()
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
for (int i = 0; i < 2; i++)
{
var bmp = new WriteableBitmap(691, 336);
var articleImg = new BitmapImage(new Uri(articles[i].ImageFilename, UriKind.Relative));
var articleImage = new Image { Source = articleImg };
articleImage.Stretch = Stretch.UniformToFill;
articleImg.CreateOptions = BitmapCreateOptions.None; // Force the bitmapimage to load it's properties so the transform will work
var bmpLogo = new WriteableBitmap(100, 100);
var logoImg = new BitmapImage(new Uri("/Assets/Tiles/FlipCycleTileSmall.png", UriKind.Relative));
var logoImage = new Image { Source = logoImg };
logoImage.Opacity = 1.0;
logoImg.CreateOptions = BitmapCreateOptions.None; // Force the bitmapimage to load it's properties so the transform will work
var articleBannerGrid = new Grid();
articleBannerGrid.Background = ColorExtensions.ToSolidColorBrush("#000F558E");
articleBannerGrid.Opacity = .5;
articleBannerGrid.Height = 100;
articleBannerGrid.VerticalAlignment = VerticalAlignment.Bottom;
articleBannerGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(100) });
articleBannerGrid.ColumnDefinitions.Add(new ColumnDefinition());
articleBannerGrid.Children.Add(logoImage);
Grid.SetColumn(logoImage, 0);
var textBlock = new TextBlock();
textBlock.Text = articles[i].Description;
textBlock.FontWeight = FontWeights.Bold;
textBlock.Margin = new Thickness(10, 5, 30, 5);
textBlock.TextWrapping = TextWrapping.Wrap;
textBlock.Foreground = new SolidColorBrush(Colors.White); //color of the text on the Tile
textBlock.FontSize = 30;
textBlock.Opacity = 1.0;
var articleTextGrid = new Grid();
articleTextGrid.Height = 100;
articleTextGrid.VerticalAlignment = VerticalAlignment.Bottom;
articleTextGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(100) });
articleTextGrid.ColumnDefinitions.Add(new ColumnDefinition());
articleTextGrid.Children.Add(textBlock);
Grid.SetColumn(textBlock, 1);
var canvas = new Grid();
canvas.Width = articleImg.PixelWidth;
canvas.Height = articleImg.PixelHeight;
canvas.Children.Add(articleImage);
canvas.Children.Add(articleBannerGrid);
canvas.Children.Add(articleTextGrid);
bmp.Render(canvas, null);
bmp.Invalidate(); //Draw bitmap
FileStream fs = new FileStream(articles[i].ImageFilename, FileMode.Create);
bmp.SaveJpeg(fs, bmp.PixelWidth, bmp.PixelHeight, 0, 100);
fs.Close();
fs.Dispose();
articleImage = null;
articleImg = null;
}
//GC.Collect();
//GC.WaitForPendingFinalizers();
ShellTile TileToFind = ShellTile.ActiveTiles.FirstOrDefault();
if (TileToFind != null)
{
string title = articles[0].Tag;
string backTitle = articles[1].Tag;
string content = articles[0].Description;
string backContent = articles[1].Description;
FlipTileData tileData = new FlipTileData()
{
Title = title,
BackTitle = backTitle,
BackContent = backContent,
WideBackContent = backContent,
BackgroundImage = new Uri(articles[0].ImageFilename, UriKind.Relative),
BackBackgroundImage = new Uri(articles[1].ImageFilename, UriKind.Relative),
WideBackgroundImage = new Uri(articles[0].ImageFilename, UriKind.Relative),
WideBackBackgroundImage = new Uri(articles[1].ImageFilename, UriKind.Relative),
};
TileToFind.Update(tileData);
}
NotifyComplete();
});
}
I assume that it is the correct place to generate the custom tile i.e. ScheduledAgent.
I found an article Dynamic Live Tile issue WP8 [WriteableBitmap] on the Nokia website with the same problem but no solution there either.
I'll continue debugging this tomorrow by removing everything and adding each bit individually step by step to see if I can spot anything but if anyone has got any suggestion or solution, I'd appreciate if you could help.
If I'm going about it the wrong way, please let me know what's the best method to update tiles in the scheduled agent.
Thanks.
WP BackgroundAgents have a small memory cap. 20MB for WP8 without update 3 and 25 for WP8 with Update 3.
I would suggest that you create a another project and add Coding4Fun toolkit and MemoryCounter in to your page and then add the code in your BackgroudAgent to the sample page and try to create the tiles. Then look at how much memory it uses. I think amount of memory usage is higher than the 20/25MB cap. if so you have to find a way to reduce it.
As usual, Microsoft is omitting critical information from its documentation.
After reducing the size of my tile from 691x336 to 336x165, it worked, so it got me thinking and I thought that the size recommended by Microsoft in this article Flip Tile template for Windows Phone 8 for wp8 and wp8.1 seemed excessive, so I did a bit more research and this is when I found this excellent explanation in an article in stackoverflow i.e.
Windows Phone 8 Startscreen Tile sizes and margins
This clearly states the size of the tiles but not based on OS versions but based on screen resolutions.
In my test environment, I was using the default 'Emulator WVGA 512MB' and again by default the screen size is 480x800, so taking into account the information provided in the answer of this article, my tile size should have been 430x210.
I resized my (already reduced) tile back up to 430x210, and it still worked.
While there is a memory cap of 20/25Mb depending on OS and patch being used, which probably causes many problems from the various articles I've read on the web, in my instance I believe it was more likely to have been affected by tile size being incorrect, and therefore the additional memory, or lack thereof.
I will update this article once I get to using 8.1 in my IDE with a larger resolution but for now, I have to assume that it was definitely related to the size of my tile.
I'll definitely make sure to add code to create tile based on os, patch and resolution being used. Bit of a pain to best honest!
Hope this helps.
The app generate an image from a UserControl into IsoStorage:
var bitmap6 = new WriteableBitmap(336, 336);
bitmap6.Render(LockscreenBG, new TranslateTransform());
var stream6 = store.CreateFile("/shared/shellcontent/LockscreenBG.jpg");
bitmap6.Invalidate();
bitmap6.SaveJpeg(stream6, 336, 336, 0, 100);
stream6.Close();
within a common Class Library - which is used by the app and the background agent - is this:
if (LockScreenManager.IsProvidedByCurrentApplication)
{
LockScreen.SetImageUri(new Uri("ms-appdata:///local/shared/shellcontent/LockscreenBG.jpg", UriKind.RelativeOrAbsolute));
}
Everything works very well but only the first time. The image still is the same without any errors. Seems like the image is not overwritten or it is used the old one from the cache???