Saving image (screenshot) of control in WPF - MVVM Pattern - c#

I'm trying to take a screenshot of user control (viewport) in WPF application. My problem is that I don't know how to get the position of window or viewport while staying in accord with MVVM pattern.
Here is the method, but it takes all the screen instead of my viewport, since I don't know how to bind window/viewport properties to it.
private void saveScreenshot()
{
double screenLeft = SystemParameters.VirtualScreenLeft;
double screenTop = SystemParameters.VirtualScreenTop;
double screenWidth = SystemParameters.VirtualScreenWidth;
double screenHeight = SystemParameters.VirtualScreenHeight;
using (Bitmap bmp = new Bitmap((int)screenWidth, (int)screenHeight))
{
using (Graphics g = Graphics.FromImage(bmp))
{
string fileName = "ScreenCapture -" + DateTime.Now.ToString("ddMMyyyy-hhmmss") + ".png";
g.CopyFromScreen((int)screenLeft, (int)screenTop, 0, 0, bmp.Size);
SaveFileDialog saveFile = new SaveFileDialog(); ;
saveFile.Filter = "JPEG|*.jpeg|Bitmap|*.bmp|Gif|*.gif|Png|*.png";
if (saveFile.ShowDialog() == true)
{
bmp.Save(saveFile.FileName);
}
}
}
}
I was trying to bind some window properties to ViewModel, to get access to window position on screen, but it didn't work.
XAML:
<Window
x:Class="SimpleDemo.MainView"
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:hx="http://helix-toolkit.org/wpf/SharpDX"
xmlns:hw="http://helix-toolkit.org/wpf"
xmlns:local="clr-namespace:SimpleDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Point cloud visualization"
Width="1280"
Height="720"
Left="{Binding LeftScreenPosition, Mode=TwoWay}"
Top="{Binding TopScreenPosition, Mode=TwoWay}"
mc:Ignorable="d" Background="#FF465065" Foreground="#FFFFFEFE">
And here is the ViewModel (SetValue contains PropertyChanged event things)
private double leftScreenPosition;
private double topScreenPosition;
public double LeftScreenPosition
{
get { return leftScreenPosition; }
set { SetValue(ref leftScreenPosition, value); }
}
public double TopScreenPosition
{
get { return leftScreenPosition; }
set { SetValue(ref leftScreenPosition, value); }
}
I would be really grateful if someone could explain to me where is the mistake, or show me another way to deal with it (Please consider that i'm a total beginner, thank you).

Trick with CommandParameter can let you pass some FrameworkElement to your command placed in your ViewModel. My example
xaml (you can refine if you need only your control in CommandParameter)
<Window x:Class="WpfTestApp.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:WpfTestApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
d:DataContext="{d:DesignInstance Type=local:YourViewModel, IsDesignTimeCreatable=True}">
<Grid>
<Button Content="Make screenShot" Command="{Binding MakeScreenShotCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>
</Grid>
ViewModel
public class YourViewModel
{
private ICommand _makeScreenShotCommand;
public ICommand MakeScreenShotCommand => _makeScreenShotCommand ?? (_makeScreenShotCommand = new ActionCommand(TakeScreenShot));
private void TakeScreenShot(object control)
{
FrameworkElement element = control as FrameworkElement;
if (element == null)
throw new Exception("Invalid parameter");
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap((int)element.ActualWidth, (int)element.ActualHeight, 96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(element);
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
using (Stream fileStream = File.Create("filePath"))
{
pngImage.Save(fileStream);
}
}
}

Related

WriteableBitmap doesn't render on Linux but works on UWP and Wasm

Im trying to to render on the UI a red box using the WriteableBitmap object . The application works as intended on UWP :
And on Linux using Gtk.Skia, nothing is being displayed (Shares the same code) :
Here is the C# code :
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
Paint();
}
private void Paint()
{
WriteableBitmap _wb = new WriteableBitmap(100, 100);
byte[] imageArray = new byte[_wb.PixelHeight * _wb.PixelWidth * 4];
for (int i = 0; i < imageArray.Length; i += 4)
{
imageArray[i] = 0;
imageArray[i + 1] = 0;
imageArray[i + 2] = 255;
imageArray[i + 3] = 255;
}
using (Stream stream = _wb.PixelBuffer.AsStream())
{
stream.Write(imageArray, 0, imageArray.Length);
}
test.Source = _wb;
}
}
XAML code :
<Page
x:Class="test.Uno.MainPage"
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"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Image x:Name="test" VerticalAlignment="Top" />
</Grid>
Support for WriteableBitmap is not yet available on Skia. Here's the related issue for this.
Update (2020-09-18): WriteableBitmap is available for Skia GTK/WPF in Uno.UI 3.1.0-dev.364 and later.

WPF print canvas with children instead of window

I want to write a WPF program wherein I want to print a specific part of the program/window. I only want the canvas to be printed.
Unfortunately the whole window gets printed all the time, but I only want the canvas to be printed.
Here is an example of the layout:
Here is my code:
public partial class MainWindow : Window
{
string name;
public MainWindow()
{
InitializeComponent();
}
private void Text(double x, double y, string text)
{
canvasPrintExample.Children.Clear();
TextBlock textBlock = new TextBlock();
textBlock.Text = text;
textBlock.Foreground = new SolidColorBrush(Colors.Black);
Canvas.SetLeft(textBlock, x);
Canvas.SetTop(textBlock, y);
canvasPrintExample.Children.Add(textBlock);
}
private void tbxClientInfoName_TextChanged(object sender, TextChangedEventArgs e)
{
clientInfoName = tbxClientInfoName.Text;
Text(0, 0, name);
}
private void btnPrintCanvas_Click(object sender, RoutedEventArgs e)
{
PrintDialog prnt = new PrintDialog();
if (prnt.ShowDialog() == true)
{
Size pageSize = new Size(prnt.PrintableAreaWidth, prnt.PrintableAreaHeight);
canvasPrintExample.Measure(pageSize);
canvasPrintExample.Arrange(new Rect(0, 0, pageSize.Width, pageSize.Height));
prnt.PrintVisual(canvasPrintExample, "Print");
}
this.Close();
}
}
I've tried changing this line in 'btnPrintCanvas_Click'
Size pageSize = new Size(canvasPrintExample.Width, canvasPrintExample.Height);
And I've tried hardcode the size:
Size pageSize = new Size(200, 400);
But nothing changes. All I get is this .pdf print:
Edit - added .xaml
<Window x:Class="Test.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:Factv001"
mc:Ignorable="d"
Title="MainWindow" Height="810" Width="1010" Background="LightGray">
<Grid>
<Canvas Name="canvasPrintExample" HorizontalAlignment="Left" Height="750" Margin="452,10,0,0" VerticalAlignment="Top" Width="530.3" Background="White"/>
<TextBox Name="tbxClientInfoName" HorizontalAlignment="Left" Height="23" Margin="10,31,0,0" TextWrapping="Wrap" Text="Name" VerticalAlignment="Top" Width="225" TextChanged="tbxClientInfoName_TextChanged"/>
<Button x:Name="btnPrintCanvas" Content="Print" HorizontalAlignment="Left" Margin="167,682,0,0" VerticalAlignment="Top" Width="75" Click="btnPrintCanvas_Click"/>
</Grid>

WPF Viewbox and Image size

I want to create the window that would show list of pictures one below the other. I've created control that contains ViewBox and Image in it:
<UserControl x:Class="..."
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>
<Viewbox Name="viewbox">
<Image Height="10" Name="image" Width="10" HorizontalAlignment="Left" VerticalAlignment="Top" />
</Viewbox>
</Grid>
</UserControl>
public BitmapImage Image
{
get { return image.Source as BitmapImage; }
set { changeImage(value); }
}
public SingleIllustrationViewer()
{
InitializeComponent();
}
private void changeImage(BitmapImage img)
{
System.Drawing.Graphics graphics = System.Drawing.Graphics.FromHwnd(IntPtr.Zero);
float dpiX = graphics.DpiX / 96;
this.image.BeginInit();
this.image.Source = img;
this.image.EndInit();
this.image.Width = img.PixelWidth / img.DpiX * dpiX;
}
and I'm placing Images on window like this:
double margin = 0;
for (int i = 0; i < illustrations.Count; i++)
{
String path = illustrations[i].printVersions.Last<String>();
BitmapImage bmp = new BitmapImage(new Uri(path));
Controls.SingleIllustrationViewer iv = new Controls.SingleIllustrationViewer();
iv.VerticalAlignment = System.Windows.VerticalAlignment.Top;
iv.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
iv.Margin = new Thickness(50, margin, 0, 0);
iv.Image = bmp;
grid.Children.Add(iv);
margin += iv.Image.Height + 20;
}
So, for example, I've placed 3 pictures (all 3 of same width) like this, and received such an interesting behavior: first one is good, second smaller, third smaller than a second. Here is the screen shot:
Maybe someone can tell me why is that so, and how can fix this, to see all those picture in the same width?
Thanks!
Regards, Tomas
Root cause:
You did not specify the Height or Width of the UserControl, so when the first SingleIllustrationViewer is added to the Grid, it will be stretched to occupied all available space until it reaches the edge of the Grid. The same happens to the second one, but it is constrained to a smaller region due to the incremented margin.
The size specified as
d:DesignHeight="300" d:DesignWidth="300"
is only used by designer, set the size like
Height="300" Width="300"
And then, put you viewers in a StackPanel instead of a Grid, then you don't have to calculate the Margin of a viewer base on the last viewer's position. StackPanel is a container that stacks its children in one direction, vertically or horizontally.
for (int i = 0; i < illustrations.Count; i++)
{
String path = illustrations[i].printVersions.Last<String>();
BitmapImage bmp = new BitmapImage(new Uri(path));
Controls.SingleIllustrationViewer iv = new Controls.SingleIllustrationViewer();
iv.VerticalAlignment = System.Windows.VerticalAlignment.Top;
iv.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
iv.Margin = new Thickness(50, 0, 0, 20); //50 px to the left, 20 px to the next child
iv.Image = bmp;
stackPanel1.Children.Add(iv);
}

Custom UserControl not showing up in ListBox

I have a problem displaying custom UserControls in my ListBox programmatically. I just can't seem to figure out what is wrong. The list-item shows up without image or text.
My project consists of:
MainWindow.xaml
MainWindow.xaml.cs
cvMenuItem.xaml
cvMenuItem.xaml.cs
Code of MainWindow.xaml.cs
private void cvMenuItem_MouseLeftButtonUp_1(object sender, MouseButtonEventArgs e)
{
lstContacts.Items.Clear();
cvMenuItem test = new cvMenuItem("test",
Environment.GetEnvironmentVariable("USERPROFILE") + #"\Downloads\images.jpg");
lstContacts.Items.Add(test);
}
Code of cvMenuItem.xaml.cs
public partial class cvMenuItem : UserControl
{
public cvMenuItem()
{
InitializeComponent();
}
public cvMenuItem(string text, string Logo)
{
this.Height = 50;
this.Width = 186;
txtService = new TextBlock() { Width = 100, Height = 50 };
imgLogo = new Image() { Width = 50, Height = 50 };
//Just found out, adding the objects as childeren partially works
this.AddChild(imgLogo);
//But I can't add txtService as Childeren
//this.AddChild(txtService);
this.Services = text;
this.Logo = Logo;
}
public string Services
{
get{ return txtService.Text.ToString() }
set
{
txtService.Text = value;
}
}
public string Logo
{
get{ return imgLogo.Source.ToString(); }
set
{
var uriSource = new Uri(value);
imgLogo.Source = new BitmapImage(uriSource);
}
}
My cvMenuItem.xaml.cs
<UserControl x:Class="WpfApplication1.cvMenuItem"
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" Height="50" Width="186">
<Grid Width="186" VerticalAlignment="Top">
<Image Name="imgLogo" Height="50" Width="50" HorizontalAlignment="Left" VerticalAlignment="Top" OpacityMask="{DynamicResource {x:Static SystemColors.ActiveCaptionTextBrushKey}}" />
<TextBlock Name="txtService" HorizontalAlignment="Left" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Bottom" Height="18" Width="121" Margin="70,0,0,18" RenderTransformOrigin="0.499,1.932"/>
</Grid>
</UserControl>
First of all you need to call InitializeComponent in the custom constructor you have added, as that is needed to process the XAML properly. Otherwise all the controls you add in the XAML will be null when running the application.
Additionally it makes no sense to create the TextBlock and Image again in the code-behind. You just have to use the ones created in the XAML.
So to get it working, change the code in the constructor to the following:
public cvMenuItem(string text, string Logo)
{
InitializeComponent();
this.Height = 50;
this.Width = 186;
this.Services = text;
this.Logo = Logo;
}

how can i show different backgrounds in multiple buttons in WPF

I need to show multiple buttons, but each one must have a different background than other buttons, I have been working on it, but I only got to display multiple buttons but with the same background.
Here is the XAML:
<Window x:Class="apple.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="370" Width="525">
<Grid>
<Image Source="C:\Users\Public\Pictures\Sample Pictures\Koala.jpg" Stretch="Fill"/>
<DockPanel Name="dock">
<UniformGrid Name="gridx" DockPanel.Dock="Top" Rows="3" Columns="3" Height="334">
</UniformGrid>
</DockPanel>
</Grid>
</Window>
Also, here is the C# code:
namespace apple
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
masterGUI();
}
public void masterGUI()
{
ImageBrush ib = new ImageBrush();
IconImage[] ico = null;
Bitmap[] img = null;
string[] list = null;
string[] link = Directory.GetFiles(#"C:\ProgramData\Microsoft\Windows\Start Menu\Programs", "*.lnk", SearchOption.AllDirectories);
list = new string[link.Length];
ico = new Icon[link.Length];
img = new Bitmap[link.Length];
for (int n = 0; n < link.Length; n++)
{
System.Windows.Controls.Button newBtn = new Button();
list[n] = System.IO.Path.GetFileNameWithoutExtension(link[n]);
FileToImageIconConverter some = new FileToImageIconConverter(link[n]);
ImageSource imgSource = some.Icon;
ib.ImageSource = imgSource;
newBtn.Background = ib;
newBtn.Content = list[n];
gridx.Children.Add(newBtn);
}
}
}
}
Any idea? thank you.
The ImageBrush needs to be created in the for-loop individually for each item. Otherwise you will end up with the same background for every item.
Also you are approaching this the "wrong" way, in WPF you should use data binding and data templating for this sort of thing instead of imperative looping.

Categories

Resources