I am writing a server side console app in C#/.Net 4.5 that gets some data and creates static chart images that are saved to be displayed by a web server.
I am mostly using the method described here:
http://lordzoltan.blogspot.com/2010/09/using-wpf-to-render-bitmaps.html
However, I added a mainContainer.UpdateLayout(); after the Arrange() so that the databindings would update and be visible in the rendered image, as well as a Measure() before it for good... ah, I'm not gonna go there.
Here is the method that does the rendering:
void RenderAndSave(UIElement target, string filename, int width, int height)
{
var mainContainer = new Grid
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
};
mainContainer.Children.Add(target);
mainContainer.Measure(new Size(width, height));
mainContainer.Arrange(new Rect(0, 0, width, height));
mainContainer.UpdateLayout();
var encoder = new PngBitmapEncoder();
var render = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
render.Render(mainContainer);
encoder.Frames.Add(BitmapFrame.Create(render));
using (var s = File.Open(filename, FileMode.Create))
{
encoder.Save(s);
}
}
The target parameter to the method will be an instance of a WPF/XAML UserControl I made - fairly simple at this point, just a grid with some text databinding to a ViewModel object that I assigned to the DataContext.
The saved image on disk looks good EXCEPT for the OxyPlot Plot object - it is entirely white.
Now, when I am in the designer in Visual Studio 2013, I can see it. I have added a design-time DataContext which is the same object that I use at runtime (this is a spike I am doing - the viewmodel is not in its final form yet, just having a bunch of default data while I work out the kinks). In the designer I see the chart as OxyPlot paints it.
Is there anything special I need to do in order to get my rendering to also contain this OxyPlot chart? It is more or less the point of the exercise so it would be awesome to actually get it to show up!
Thanks in advance for any insights and suggestions!
If you're correctly binding data at runtime as well, then it should work.
[STAThread]
static void Main(string[] args)
{
string filename = "wpfimg.png";
RenderAndSave(new UserControl1(), filename, 300, 300);
PictureBox pb = new PictureBox();
pb.Width = 350;
pb.Height = 350;
pb.Image = System.Drawing.Image.FromFile(filename);
Form f = new Form();
f.Width = 375;
f.Height = 375;
f.Controls.Add(pb);
f.ShowDialog();
}
XAML:
<UserControl x:Class="WpfApp92.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"
xmlns:oxy="http://oxyplot.org/wpf"
xmlns:local="clr-namespace:WpfApp92"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<oxy:PlotView Model="{Binding Model}"/>
</Grid>
</UserControl>
CS:
public partial class UserControl1 : UserControl
{
public PlotModel Model { get; set; }
public UserControl1()
{
InitializeComponent();
Model = new PlotModel();
Model.LegendBorderThickness = 0;
Model.LegendOrientation = LegendOrientation.Horizontal;
Model.LegendPlacement = LegendPlacement.Outside;
Model.LegendPosition = LegendPosition.BottomCenter;
Model.Title = "Simple model";
var categoryAxis1 = new CategoryAxis();
categoryAxis1.MinorStep = 1;
categoryAxis1.ActualLabels.Add("Category A");
categoryAxis1.ActualLabels.Add("Category B");
categoryAxis1.ActualLabels.Add("Category C");
categoryAxis1.ActualLabels.Add("Category D");
Model.Axes.Add(categoryAxis1);
var linearAxis1 = new LinearAxis();
linearAxis1.AbsoluteMinimum = 0;
linearAxis1.MaximumPadding = 0.06;
linearAxis1.MinimumPadding = 0;
Model.Axes.Add(linearAxis1);
var columnSeries1 = new ColumnSeries();
columnSeries1.StrokeThickness = 1;
columnSeries1.Title = "Series 1";
columnSeries1.Items.Add(new ColumnItem(25, -1));
columnSeries1.Items.Add(new ColumnItem(137, -1));
columnSeries1.Items.Add(new ColumnItem(18, -1));
columnSeries1.Items.Add(new ColumnItem(40, -1));
Model.Series.Add(columnSeries1);
var columnSeries2 = new ColumnSeries();
columnSeries2.StrokeThickness = 1;
columnSeries2.Title = "Series 2";
columnSeries2.Items.Add(new ColumnItem(12, -1));
columnSeries2.Items.Add(new ColumnItem(14, -1));
columnSeries2.Items.Add(new ColumnItem(120, -1));
columnSeries2.Items.Add(new ColumnItem(26, -1));
Model.Series.Add(columnSeries2);
DataContext = this;
}
}
I don't know anything about this OxyPlat, but I do know that most charts are often rendered using hardware APIs. Hardware acceleration is usually error-prone when working outside the expected environment (i.e. a client showing a visible Desktop window).
On application initialization, try disabling hardware acceleration:
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
Another possible tweak is to call DoEvents() before you render your bitmap. Possibly with priority set to SystemIdle. This will make sure your DataContext has been successfully bound.
public static void DoEvents(
DispatcherPriority priority = DispatcherPriority.Background)
{
Dispatcher.CurrentDispatcher.Invoke(new Action(delegate { }), priority);
}
Related
I have a set of procedural images that I would like to add as billboards to my helix 3D application.
Currently my application looks as following:
public partial class _3DControl
{
HelixViewport3D hVp3D;
public _3DControl()
{
InitializeComponent();
createView();
}
public void createView()
{
hVp3D = new HelixViewport3D();
var lights = new SunLight();
lights.Altitude=40;
lights.Ambient=0.4;
this.Content = hVp3D;
hVp3D.Children.Add(lights);
this.Show();
}
public void UploadBillboard(BitmapImage im, System.Windows.Media.Media3D.Point3D position,double width,double height)
{
//create material
var mat = MaterialHelper.CreateImageMaterial(im, 0);
var bboard = new BillboardVisual3D();
bboard.Material = mat;
//set coordinates
bboard.Position = position;
bboard.Width = width;
bboard.Height = height;
//add the billboard
hVp3D.Children.Add(bboard);
}
However when I call the function to add a billboard:
HelixLinker.GetHelix().UploadBillboard(((Bitmap)e).bitmapToBitmapImage(),
new System.Windows.Media.Media3D.Point3D(0, 0, 0), 100, 100);
Then I see nothing being added, any idea what I' m doing wrong?
I also tried with the RectangleVisual3D class.
public void UploadRect(BitmapImage im, System.Windows.Media.Media3D.Point3D position, double width, double height)
{
var mat = MaterialHelper.CreateImageMaterial(im, 0);
var bboard = new RectangleVisual3D ();
bboard.Material = mat;
bboard.Width = width;
hVp3D.Children.Add(bboard);
}
Which if execuded in the same way results in a (promising) image however in this case the material appears not to be properly set.
Note: I hope that the BillboardVisual3D is the right class, I'm working on something that will allow me to put image "on the floor" so to speak, I want to have flat images that don't have a depth and allow for transparancy.
Why do you set the Opacity to 0? Then you wont see anything.
Try this:
//create material
var mat = MaterialHelper.CreateImageMaterial(im);
See the documentation
Edit:
Sorry, my bad with the string. But this ist working for me:
Code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
CreateBilboard();
}
private void CreateBilboard()
{
BitmapImage im = new BitmapImage(new System.Uri(#"C:\Users\Public\Pictures\Sample Pictures\Lighthouse.jpg"));
var mat = MaterialHelper.CreateImageMaterial(im, 1);
var bboard = new BillboardVisual3D();
bboard.Material = mat;
var position = new System.Windows.Media.Media3D.Point3D(0, 0, 0);
var width = 100;
var height = 100;
//set coordinates
bboard.Position = position;
bboard.Width = width;
bboard.Height = height;
//add the billboard
viewPort.Children.Add(bboard);
}
}
XAML:
<Window x:Class="BillboardImageMaterialDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ht="clr-namespace:HelixToolkit.Wpf;assembly=HelixToolkit.Wpf"
Title="BillboardImageMaterialDemo" Height="480" Width="640">
<Grid>
<ht:HelixViewport3D x:Name="viewPort" ZoomExtentsWhenLoaded="True">
<!-- Remember to add some lights -->
<ht:SunLight/>
</ht:HelixViewport3D>
</Grid>
</Window>
Result:
I want to take a snapshot of my UserControl, which has not been shown yet.
That's my code:
public Screenshot(MyViewModel viewModel)
{
if (viewModel == null)
return;
// Create a TabControl, where View is hosted in
var visualTab = new TabControl();
visualTab.Width = FrameworkAdjustments.VisualSize.Width;
visualTab.Height = FrameworkAdjustments.VisualSize.Height;
visualTab.TabStripPlacement = Dock.Left;
// Tabs should only be shown, if there are more than one 'SubView'
Visibility tabVisibility = Visibility.Collapsed;
if (viewModel.SubViews.Count > 1)
tabVisibility = Visibility.Visible;
foreach (var subView in viewModel.SubViews)
{
var tab = new TabItem();
tab.Header = subView.TranslatedId; // multilanguage header
tab.Visibility = tabVisibility;
if (subView.Id == viewModel.ActiveSubView.Id)
{
tab.IsSelected = true;
// Without the following line my UI works, but my TabControl is empty.
tab.Content = ViewManager.GetViewById(subView.Id);
// ViewManager.GetViewById(subView.Id); returns a UserControl
}
tab.Measure(FrameworkAdjustments.VisualSize);
tab.Arrange(new Rect(FrameworkAdjustments.VisualSize));
visualTab.Items.Add(tab);
}
_ContentCtrl = new ContentControl() { Width = FrameworkAdjustments.VisualSize.Width, Height = FrameworkAdjustments.VisualSize.Height };
_ContentCtrl.Content = visualTab;
_ContentCtrl.Measure(FrameworkAdjustments.VisualSize);
_ContentCtrl.Arrange(new Rect(FrameworkAdjustments.VisualSize));
RenderTargetBitmap bmp = new RenderTargetBitmap((int)FrameworkAdjustments.VisualSize.Width, (int)FrameworkAdjustments.VisualSize.Height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(_ContentCtrl);
this.ItemBrush = new ImageBrush(bmp);
}
This code runs for each 'MyViewModel' when I start my App.
'MyViewModel' contains a List of 'SubViews' which are the content of the Tabs and they contain a 'FunctionKeyBar' which's buttons can be activated by using 'F1' to 'F12'. But after creating my screenshot I can't use the F1 to F12 anymore. Also there are other problems, like switch language.
Is there an other way to create a snapshot of a control which has not came into view?
Thanks for all replys.
Greetings Benny
Approach : Set Margin to negative to keep it hidden, Add the control to the Grid / any other container.
Follow these steps :
1) Create a Task to create and add your ContentControl to the Grid.
2) Call user-define CaptureScreen () function.
3) Visibility must not be Hidden/Collapsed. Margin can be negative to hide the control.
In this example, I have done this in a Button.Click.
async private void Button_Click(object sender, RoutedEventArgs e)
{
Task<ContentControl> t = AddContentControl();
ContentControl ctrl = await t;
RenderTargetBitmap bmp = CaptureScreen(ctrl, 5000, 5000);
Img.Source = bmp;
}
/* Add the ContentControl to the Grid, and keep it hidden using neg. Margin */
private Task<ContentControl> AddContentControl()
{
Task<ContentControl> task = Task.Factory.StartNew(() =>
{
ContentControl ctrl = null;
Dispatcher.Invoke(() =>
{
ctrl = new ContentControl() { Content = "Not shown", Width = 100, Height = 25, Margin = new Thickness(-8888, 53, 0, 0) };
Grd.Children.Add(ctrl);
});
return ctrl;
});
return task;
}
/* Important , wont work with Visibility.Collapse or Hidden */
private static RenderTargetBitmap CaptureScreen(Visual target, double dpiX, double dpiY)
{
if (target == null)
{
return null;
}
Rect bounds = VisualTreeHelper.GetDescendantBounds(target);
RenderTargetBitmap rtb = new RenderTargetBitmap((int)(bounds.Width * dpiX / 96.0),
(int)(bounds.Height * dpiY / 96.0),
dpiX,
dpiY,
PixelFormats.Pbgra32);
DrawingVisual dv = new DrawingVisual();
using (DrawingContext ctx = dv.RenderOpen())
{
VisualBrush vb = new VisualBrush(target);
ctx.DrawRectangle(vb, null, new Rect(new Point(), bounds.Size));
}
rtb.Render(dv);
return rtb;
}
Now I found a solution, thanks to AnjumSKan.
I took his code and changed it a little bit. As I said, I need to create the snapshot on application startup or culture changed and I have more than one view. in my Function AddContentControl, I add the Content to my TabControl which has negative Margin. Add the end I call HiddenTab.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Render, new Action(CreateSnapshotOnRender));
('HiddenTab' is my TabControl which is not shown to user). This calls a function called CreateSnapshotOnRender, where I call AnjumSKhan's CaptureScreen-method from. After that I call again the function AddContentControl with my next content. This looks as following:
private void CreateScreenshotOnRender()
{
HiddenTab.Measure(ViewSize);
HiddenTab.Arrange(new Rect(ViewSize));
var snapshot = CaptureScreen(HiddenTab, dpiX, dpiY);
/* Do anything with snapshot */
_Index++; // counter to know thich view is next
CreateAllScreenshots();
}
Thanks again to AnjumSKan, because you lead me to this. Thats why I marked your Answer as the correct one.
I want to create a multiple inkcanvases with tooltip displayes.so i want to create ink canvas class dynamically.whenever i create instance of inkcanvas class ,a new ink canvas with tool display have to be created in WPF window.
class1 mycanvas1 = new class1(" aa");
class1 mycanvas2 = new class1("bb")
The letter in the string is the text of tool tip display.can u tell me the way .
I created a ink canvas usercontrol with tooltip empty text.but i unable to call this wpf user control in the above way.
InkCanvas Customized class with ToolTip:
[DebuggerDisplay("[{Scene}]Strokes:{Strokes.Count}, Children:{Children.Count}")]
public class InkCanvas_SandeepJadhav : InkCanvas
{
public InkCanvas_SandeepJadhav(string toolTip)
{
ToolTip = toolTip;
}
}
Inkcanvas class created dynamically.
public partial class MainWindow : Window
{
public InkCanvas_SandeepJadhav currCanvas = null;
double width = 0, height = 0, toolWindowHeight = 0;
public MainWindow()
{
InitializeComponent();
this.WindowState = System.Windows.WindowState.Maximized;
width = System.Windows.SystemParameters.WorkArea.Width;
height = System.Windows.SystemParameters.WorkArea.Height;
currCanvas = new InkCanvas_SandeepJadhav("Sandy");
currCanvas.Width = width;
currCanvas.Height = height - 150;
currCanvas.Background = (System.Windows.Media.Brush)new SolidColorBrush(Colors.Lime);
toolWindowHeight = (height / 10);
currCanvas.Margin = new Thickness(0, 0, 0, toolWindowHeight);
myGrid.Children.Add(currCanvas);
}
}
XAML Code
<Window x:Class="WpfMultiInkCanvas.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 x:Name="myGrid"></Grid>
</Window>
here my code
namespace strokecollectio
{
class mycan : InkCanvas
{
public mycan()
{
this.Width = 300;
this.Height = 200;
}
}
}
I am trying to understand why a ScaleTransform (testing under Windows Phone 8.0) is rendering using the control's parent dimensions, instead of the control itself. I have set up a demo app that shows this behaviour, as shown below.
The dimensions and hierarchy has a reason to be, that's why I manually set the Width and Height, as it's very close to the real app.
What I'd expect is that child1 (the yellow one), which is 768x1228 and has a scale of 0.625, would render itself as if it was 480x768, but what happens is that it renders like it was 300x480 (e.g., it is rendered at 0.625% of 480, instead of 768).
Any clues?
public partial class MainPage : PhoneApplicationPage
{
private Grid root;
public MainPage()
{
InitializeComponent();
root = new Grid() {
Width = 480,
Height = 768,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Background = new SolidColorBrush(Colors.Blue)
};
Content = root;
Loaded += MainPage_Loaded;
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var parent1 = new Grid {
Background = new SolidColorBrush(Colors.Red),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Width = 480,
Height = 768
};
root.Children.Add(parent1);
var child1 = new Grid {
Background = new SolidColorBrush(Colors.Yellow),
Width = 768,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Height = 1228,
RenderTransform = new ScaleTransform() {
CenterX = 0,
CenterY = 0,
ScaleX = 0.625,
ScaleY = 0.625
}
};
parent1.Children.Add(child1);
}
}
Well, looks like it's all due to the fact that the Grid component clip its child elements, as mentioned at http://wpf.2000things.com/tag/clipping. After I changed the code to have a Canvas aground my Grid, the app worked as expected.
I still find this "solution" strange, though.
Since I am not getting anywhere with my previous question, I would like to know, are there any ways I can create icons on the fly on WPF?
You don't need WPF.
Usign GDI+ (System.Drawing.dll), you can create a 16x16 Bitmap, then call Icon.FromHandle(bitmap.GetHicon()).
You can use WritableBitmap for this.
You can use Taskbar icon progress bar...Sure U have seen, most of the application if doing any scanning or progress things, it displays progress actions on Icon.
Do this in your main form where u included the Icon
<Window x:Class="CCTrayHelper.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Icon="/CCTrayHelper;component/Images/CCTrayHelperIcon.png">
<Window.TaskbarItemInfo>
<TaskbarItemInfo />
</Window.TaskbarItemInfo>
Trig it from code behind
private void OnProgress(object sender, EventArgs args)
{
Dispatcher.Invoke(DispatcherPriority.Send, (Action)delegate() { TaskbarItemInfo.ProgressState = TaskbarItemProgressState.None; });
// Use here your progress type
}
This is what I ended up doing, I don't fully understand the details, if you find anything that can be improved, please do comment. Thanks for all the answers and comments.
public static ImageSource GetIconWithText(int digit)
{
BitmapImage myBitmapImage = new BitmapImage(new Uri(#"Images\PomoDomo.ico",
UriKind.RelativeOrAbsolute));
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
// Draw image
drawingContext.DrawImage(myBitmapImage, new Rect(0, 0, myBitmapImage.Width,
myBitmapImage.Height));
var typeFace = new Typeface(new FontFamily("Verdana"), FontStyles.Normal,
FontWeights.ExtraBold, FontStretches.UltraCondensed);
var formatedText = new FormattedText(digit.ToString(),
CultureInfo.InvariantCulture,
FlowDirection.LeftToRight,
typeFace,
40,
System.Windows.Media.Brushes.White);
//Center the text on Image
int pointY = (int)(myBitmapImage.Height - formatedText.Height) / 2;
int pointX = (int)(myBitmapImage.Width - formatedText.Width) / 2;
drawingContext.DrawText(formatedText, new Point(pointX, pointY));
}
RenderTargetBitmap finalBitmap = new RenderTargetBitmap((int)myBitmapImage.Width,
(int)myBitmapImage.Height, myBitmapImage.DpiX, myBitmapImage.DpiY,
PixelFormats.Pbgra32);
finalBitmap.Render(drawingVisual);
return finalBitmap;
}
private static void SaveImage(RenderTargetBitmap returnBitmap, string pngFileName)
{
string fileName = string.Format("{0}.png", pngFileName)
PngBitmapEncoder image = new PngBitmapEncoder();
image.Frames.Add(BitmapFrame.Create(returnBitmap));
using (Stream fs = File.Create(fileName))
{
image.Save(fs);
}
}
You can find a method here and here to render text on writeablebitmap