Saving FrameworkElement with its DataContext to image file does no succeed - wpf

I have a simple UserControl called UserControl1 that contains a TextBlock:
<UserControl x:Class="WpfApplication2.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="{Binding}"/>
</Grid>
</UserControl>
I initialized a new instance of it and gave it a DataContext in code. when the window is closing I have to draw this control to an image file.
The UserControl does not render the bounded text in the file that been created.
and this is my code using the usercontrol:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Closing += MainWindow_Closing;
}
void MainWindow_Closing(object sender, CancelEventArgs e)
{
UserControl1 uc = new UserControl1();
uc.DataContext = "hello";
uc.Height = 100;
uc.Width = 100;
uc.Background = Brushes.LightBlue;
DrawToImage(uc);
}
private void DrawToImage(FrameworkElement element)
{
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
element.Arrange(new Rect(element.DesiredSize));
RenderTargetBitmap bitmap = new RenderTargetBitmap((int)element.Width, (int)element.Height,
120.0, 120.0, PixelFormats.Pbgra32);
bitmap.Render(element);
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (Stream s = File.OpenWrite(#"C:\555.png"))
{
encoder.Save(s);
}
}
}
I Hope It's clear enough, any help will be very appreciated.

You just forgot to force a Layout update on your control after manually Measuring/Arrangeing it (which will not be enough to force binding resolving).
A simple call to UpdateLayout makes it work :
private void DrawToImage(FrameworkElement element)
{
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
element.Arrange(new Rect(element.DesiredSize));
element.UpdateLayout();
RenderTargetBitmap bitmap = new RenderTargetBitmap((int)element.Width, (int)element.Height,
120.0, 120.0, PixelFormats.Pbgra32);
bitmap.Render(element);
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (Stream s = File.OpenWrite(#"C:\555.png"))
{
encoder.Save(s);
}
}
Edit : More on when bindings are resolved : link

Try to call the function SaveImage() on userControl1.Loaded event

If I do this it works, not sure this is what you want though:
<Window x:Class="DrawImage.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:DrawImage="clr-namespace:DrawImage"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DrawImage:UserControl1 x:Name="uc" Visibility="Hidden"/>
</Grid>
</Window>
void MainWindow_Closing(object sender, CancelEventArgs e)
{
uc.DataContext = "hello";
uc.Height = 100;
uc.Width = 100;
uc.Background = Brushes.LightBlue;
uc.Visibility = Visibility.Visible;
DrawToImage(uc);
}

EDIT
I am now able to reproduce the issue. If I set the DataContext in the Window contstructor then it works. If I set it in the Winndow_Closed event I get the exact same result that you get.
I guess there might no workaround since the WPF needs some time to actually render the text on the UI thread. If you render the PNG before the WPF has rendered the text on the UI thread it will not appear on the PNG.
A workaround does not seem to exist since the window will be destroyed when the Closed event handlers has been running. There is no way to block the UI thread on the one hand to prvevent to window from beeing detroyed when you on the other hand want the UI thread to render the control.
I'd suggest to render the image as soon as the control has been rendered and save the image file when the window is closed.

I posted an Article in my Blog (putting in the account the png Transparancy (causes the black background)):
Saving FrameworkElement as Image
FrameworkElement element = myControl.Content;
// you can set the size as you need.
Size theTargetSize = new Size(1500,2000)
element.Measure(new System.Windows.Size(double.PositiveInfinity, double.PositiveInfinity));
element.Arrange(new Rect(theTargetSize ));
// to affect the changes in the UI, you must call this method at the end to apply the new changes
element.UpdateLayout();
You can find the full cod in the Blog Post.

Related

WPF Window: Need to use SizeToContent up to a specific Size

I have a WPF window with content that can vary it's size (a DataGrid).
SizeToContent works great for small Datasets, but for large Datasets the window gets too big on initial display.
How would I use SizeToContent in conjuction with a Maximum initial size of 600x600 (the window should still be able to be resized larger manually or maximized). Also the window can be hidden and reshown multiple times and should retain it's size when reshown (that is already the default behavior I'd like to retain).
myWindow.SizeToContent = SizeToContent.WidthAndHeight;
It sounds like what you want is to temporarily set the MaxWidth and MaxHeight on first render, but then turn it off after the first render. The problem is, if you turn off those values after the first render, then your SizeToContent will kick in and the window will resize to the large Dataset again!
To alleviate this, you will also have to turn off your SizeToContent after the first render. Here's a solution that worked for me:
MainWindow.xaml:
<Window x:Class="_72120715.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:_72120715"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" SizeToContent="WidthAndHeight" MaxWidth="600" MaxHeight="600">
<Grid>
<ListBox ItemsSource="{Binding Stuff}"/>
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public ObservableCollection<string> Stuff { get; set; }
public MainWindow()
{
InitializeComponent();
ContentRendered += MainWindow_ContentRendered;
DataContext = this;
Stuff = new ObservableCollection<string>();
for (int i = 0; i < 100; i++)
{
Stuff.Add($"This is stuff {i}");
}
}
private void MainWindow_ContentRendered(object sender, EventArgs e)
{
// Clear the MaxWidth/MaxHeight so that you can manually resize larger
MaxWidth = double.PositiveInfinity;
MaxHeight = double.PositiveInfinity;
// Clear the SizeToContent so that it doesn't automatically resize to large datasets
SizeToContent = SizeToContent.Manual;
}
}

canvas draw shape on MouseMove lag

I have very simple scenario: there is a canvas and I need to draw a line on canvas using MouseMove. But when I move the mouse pointer, second line's point (which is set in mouse move) doesn't match current mouse position.
UPD 2:
Delta depends on speed of mouse, if speed is large - delta is large and noticeable(lag). I've noticed that this bug is more visible if you move your mouse not very fast and not very slow.
You can download sample project here.
Something like on the picture when mouse moves fast:
Some source code:
<Window x:Class="WpfApplication32.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<Canvas x:Name="MainCanvas"
MouseLeftButtonDown="MainCanvas_OnMouseLeftButtonDown"
MouseMove="MainCanvas_OnMouseMove"
Background="White"
/>
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfApplication32
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Line _currentLine;
private bool _isDrawing;
public MainWindow()
{
InitializeComponent();
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
MainCanvas.Focus();
}
private void MainCanvas_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (_isDrawing)
{
_currentLine = null;
_isDrawing = false;
return;
}
_isDrawing = true;
_currentLine = new Line(){Stroke = Brushes.Green};
var p = e.GetPosition(MainCanvas);
_currentLine.X1 = p.X;
_currentLine.Y1 = p.Y;
_currentLine.X2 = p.X;
_currentLine.Y2 = p.Y;
MainCanvas.Children.Add(_currentLine);
}
private void MainCanvas_OnMouseMove(object sender, MouseEventArgs e)
{
if (_currentLine == null)
return;
var p = e.GetPosition(MainCanvas);
_currentLine.X2 = p.X;
_currentLine.Y2 = p.Y;
}
}
}
I've tried to use CompositeTarget.Render, also timer to change second point every 20ms but it didn't help.
I have legacy project in which code depends a lot on this approach(canvas mouseMove and shapes). So I need easiest way to eliminate this lag or some ideas about a reason of this bug) Thanks.
UPD:
I've tried to record video with this problem but I'm not good at it. Here is some screen from my recorded to show the problem:
http://prntscr.com/64hueg
UPD 2:
I've tried to use OnRender of Window object to do the same without canvas. I've used DataContext to draw the line - same issue here. DataContext is considered faster than Canvas and Line (Shape). So this is not Canvas issue.
I've also tried to use WritableBitmap to draw the line - no difference.
I thought that there might be a problem with MouseMove event - I read if there is a lot of objects(not my case but still) MouseMove might fire with delays so I used Win32 WM_MOUSEMOVE but it didn't helped as well. In my case delay between MW_MOUSEMOVE and wpf MouseMove event was <1000 ticks.
The only answer I see so far is render delay. I don't know how to improve it because it is wpf internals =(.
By the way Paint.net seems to use wpf and this problem occurs there as well.
This cannot be fixed because this is due to WPF's internal render system. There will always be a lag even if visual tree is simple. Complex visual tree results to more delay. I've spend a lot of time trying solving this.
Try using MouseMove event not on main canvas but on Window itself.
I recently got same problem using MouseMove on Image and it was laggy as hell
Switching to window's event helped me a lot.
<Window x:Name="Window1" x:Class="WpfApp2.MViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MViewer" Height="454.411" Width="730.515" Loaded="Window_Loaded" Closing="Window1_Closing" ContentRendered="Window1_ContentRendered" MouseMove="Window1_MouseMove">
<Grid>
<Image x:Name="Image1" MouseMove="Image1_MouseMove"/>
<Line Name="Line1" Visibility="Visible" Stroke="Red" StrokeThickness="0.75" />
<Line Name="Line2" Visibility="Visible" Stroke="Red" StrokeThickness="0.75" />
</Grid>
</Window>
and
private void Window1_MouseMove(object sender, MouseEventArgs e)
{
Line1.Visibility = Visibility.Visible;
Line1.X1 = Mouse.GetPosition(this).X;
Line1.X2 = Mouse.GetPosition(this).X;
Line1.Y1 = 0;
Line1.Y2 = Window1.Height;
Line2.Visibility = Visibility.Visible;
Line2.X1 = 0;
Line2.X2 = Window1.Width;
Line2.Y1 = Mouse.GetPosition(this).Y;
Line2.Y2 = Mouse.GetPosition(this).Y;
}

WPF, Displaying Emgu Image, using Binding

I am familiar with basic code to display an Emgu image in a WPF image box, when all the source code is in the MainWindow.xaml.cs code-behind.
However I am trying to place my Emgu-related code, including the "ProcessFrame" event / Queryframe snippet, into a separate class of static methods so that I can reuse them later. I am doing this because while I will want to be able to grab images from the same camera at a later stage, I also want the flexibility to display those images in a different image box. I am having trouble with this step.
If I could bind the Image box dynamically to a property in the static method (and also enable / disable that binding programmatically), I think that would solve my problem. However, there may be some other problem with the approach I am trying to take. Any code / xaml modifications greatly appreciated.
The following code works, but is unsatisfactory because it forces me to bundle ProcessFrame method into the MainWindow code:
XAML (working):
<Window x:Class="EmguWPF_Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Image Height="215" HorizontalAlignment="Left" Margin="62,66,0,0" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="224" />
</Grid>
</Window>
MainWindow Code Snippet (working):
//using statements etc
public partial class MainWindow : Window
{
private Image<Bgr, Byte> image;
private Capture capture = null;
private void button1_Click(object sender, RoutedEventArgs e)
{
InitializeCameras();
timer = new DispatcherTimer();
timer.Tick+=new EventHandler(ProcessFrame);
timer.Interval = new TimeSpan(0, 0, 0, 0, 10);
timer.Start();
}
private void InitializeCameras()
{
if (capture == null)
{
try
{
capture = new Capture(0);
}
catch // etc
}
}
private void ProcessFrame(object sender, EventArgs arg)
{
image = capture.QueryFrame();
image1.Source = BitmapSourceConvert.ToBitmapSource(image);
}
}
public static class BitmapSourceConvert
{
[DllImport("gdi32")]
private static extern int DeleteObject(IntPtr o);
public static BitmapSource ToBitmapSource(IImage image)
{
using (System.Drawing.Bitmap source = image.Bitmap)
{
IntPtr ptr = source.GetHbitmap(); //obtain the Hbitmap
BitmapSource bs = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
ptr,
IntPtr.Zero,
Int32Rect.Empty,
System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
DeleteObject(ptr); //release the HBitmap
return bs;
}
}
}
The following code is where I am up to but need help:
XAML (same as before)
<Window x:Class="EmguWPF_Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Image Height="215" HorizontalAlignment="Left" Margin="62,66,0,0" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="224" />
</Grid>
</Window>
ViewModel Snippet (yes - perhaps too ambitious to be experimenting with design patterns):
public ViewModel()
{
CaptureMethods.InitializeCameras();
timer = new DispatcherTimer();
timer.Tick += new EventHandler(CaptureMethods.ProcessFrame);
timer.Interval = new TimeSpan(0, 0, 0, 0, 10);
timer.Start();
}
CaptureMethods class, not working as a separate class in the way I want it to. You will notice I am now defining the capture field in this class, not in the ViewModel class:
class CaptureMethods
{
private static Capture capture = null;
public static void InitializeCameras()
{
if (capture == null)
{
try
{
capture = new Capture(0);
}
catch // etc
}
}
public static void ProcessFrame(object sender, EventArgs arg)
{
image = capture.QueryFrame();
image1.Source = BitmapSourceConvert.ToBitmapSource(image); // this is my problem line
}
}
// BitmapSourceConvert class not repeated here to avoid duplication.
Thanks!
My suggestion is not to use the WPF Image Box, but the Emgu's ImageBox (Emgu.CV.UI.ImageBox). It is a more complete control and it is designed to use with the framework.
The only problem is that type of control only works with Windows Forms, but you can always create a WinForms User Control with a Emgu's Image Box and use it in WPF inside a WindowsFormsHost.
To expand a bit on celsoap7's answer here is what the resulting XAML might look like:
<Window x:Class="WPFEmguCV.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:emui="clr-namespace:Emgu.CV.UI;assembly=Emgu.CV.UI"
Title="MainWindow" Height="521" Width="1274">
<Grid>
<WindowsFormsHost>
<emui:ImageBox x:Name="CapturedImageBox" Width="409" Height="353" />
</WindowsFormsHost>
</Grid>
</Window>
I (and others) found that marshaling the images onto the UI thread takes too much CPU and so you are better off doing as celsoap7 suggests and put an EmguCV ImageBox inside a WPF WindowsFormsHost.
Sadly that may make the kind of MVVM binding you are asking about quite different from the structure you envisage.

How to draw WPF Adorners on top of everything else?

I've added an Adorner to my DateTimePicker control but it's not shown on top of the other controls. Why? How do I fix it?
My XAML currently goes like this:
<UserControl x:Class="IntelliMap.WPF.DateTimePicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:wpftc="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
mc:Ignorable="d">
...
<AdornerDecorator>
<Grid>
...
<TextBox x:Name="DateDisplay"
HorizontalAlignment="Stretch" ...>
</TextBox>
...
</Grid>
</AdornerDecorator>
</UserControl>
The adorner itself is a separate class from the UserControl and added in the constructor:
public DateTimePicker()
{
InitializeComponent();
...
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(DateDisplay);
if (adornerLayer != null)
{
adornerLayer.Add(_upDownBtns = new TextBoxUpDownAdorner(DateDisplay));
_upDownBtns.Click += (textBox, direction) => { OnUpDown(direction); };
}
}
The problem apparently occurs because the Adorners governed by the AdornerDecorator are only guaranteed to appear on top of the controls inside the AdornerDecorator. It is necessary to wrap most of the contents of the window in an AdornerDecorator instead, but after doing this, AdornerLayer.GetAdornerLayer() apprently can't see the AdornerDecorator under some circumstances and returns null.
The documentation claims "GetAdornerLayer walks up the visual tree, starting at the specified UIElement, and returns the first adorner layer it finds." In reality, GetAdornerLayer cannot find an AdornerDecorator located outside of the UserControl, at least not in .NET 3.5. I fixed the problem by doing exactly what GetAdornerLayer claims to do itself:
static AdornerLayer GetAdornerLayer(FrameworkElement subject)
{
AdornerLayer layer = null;
do {
if ((layer = AdornerLayer.GetAdornerLayer(subject)) != null)
break;
} while ((subject = subject.Parent as FrameworkElement) != null);
return layer;
}
public DateTimePicker()
{
InitializeComponent();
...
this.Loaded += (s, e) =>
{
// not null anymore!
AdornerLayer adLayer = GetAdornerLayer(DateDisplay);
};
}
Finally, GetAdornerLayer must be called from the Loaded event instead of the constructor.
There's already an adorner layer in the default Window style, and that adorner layer sits above the content of the window.
So just remove the AdornerLayer from the UserControl and that should work.

Silverlight/WPF: Retreiving the size of a UIElement once it has been rendered on screen

I have the following simple piece of code:
var canvas = new Canvas();
foreach (var ztring in strings)
{
var textblock = new TextBlock();
textblock.Text = ztring;
panel.Children.Add(textblock);
textblock.Measure(infiniteSize);
}
At this point I would expect any of the size properties (Height/Width, ActualHeight/ActualWidth, DesiredSize, RenderSize) to give me the size of the textblock. None of them do.
ActualHeight always gives 16.0 no matter what size font. ActualWidth changes according to the text length but not the font size.
I change the font size on the parent container and not the TextBlock itself.
I feel like I am missing some basic element of understanding the manipulation of silverlight elements from within the codebehind.
The question is: how do I get the real actual pixel size of my TextBlock?
Below is a sample that adds a TextBlock to a Canvas using code behind and once the TextBlock is rendered, it displays its height in the title of the window. Is that what you are looking for?
XAML:
<Window x:Class="HeightTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<StackPanel TextBlock.FontSize="30">
<Canvas Name="_canvas" Height="200"/>
</StackPanel>
</Window>
Code behind:
using System.Windows;
using System.Windows.Controls;
namespace HeightTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
TextBlock textBlock = new TextBlock();
textBlock.Text = "Hello";
Canvas.SetLeft(textBlock, 25);
textBlock.Loaded +=
(sender, e) =>
{
Title = textBlock.ActualHeight.ToString();
};
_canvas.Children.Add(textBlock);
}
}
}
Have you tried using a real container like a Grid instead of Canvas?
What if you try reading the ActualSize property after the Measure using a Dispatcher.BeginInvoke?

Resources