WPF RenderTargetBitmap returns a black image - c#

I have to "export" a UserControl as an image, I've tried to use RenderTargetBitmap, but this outputs a black image. I tried online solution, but I seem to be missing something.
Here is my code:
UserControl usage :
<uc:ImageStatusBar x:Name="OverlayPanel" Grid.Column="2" Grid.Row="1" HorizontalAlignment="Stretch"/>
UserControl code :
<UserControl x:Class="ImageViewer.Controls.ImageStatusBar"
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:local="clr-namespace:ImageViewer.Controls"
xmlns:uc="clr-namespace:ImageViewer.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid x:Name="StatusbarGrid" Grid.Column="2" Grid.Row="1" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="8*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StatusBar x:Name="LeftSideStaticStatusBar" Grid.Column="0">
<StatusBar.Background>
<SolidColorBrush Color="White" Opacity="0.5"/>
</StatusBar.Background>
<StatusBarItem x:Name="Logo" Height="42" Width="42" HorizontalAlignment="Right" Padding="6,0,0,0">
<Image x:Name="LogoImage"></Image>
</StatusBarItem>
</StatusBar>
<StatusBar x:Name="StatusBar" ItemsSource="{Binding}" Grid.Column="1" Padding="6,0,0,0">
<StatusBar.Background>
<SolidColorBrush Color="White" Opacity="0.5"/>
</StatusBar.Background>
<StatusBar.ItemTemplate>
<DataTemplate>
<Border BorderThickness="4 0 4 0">
<uc:StatusBarItem HorizontalAlignment="Left"/>
</Border>
</DataTemplate>
</StatusBar.ItemTemplate>
</StatusBar>
<StatusBar x:Name="RightSideStaticStatusBar" Grid.Column="2">
<StatusBar.Background>
<SolidColorBrush Color="White" Opacity="0.5"></SolidColorBrush>
</StatusBar.Background>
<StatusBarItem x:Name="IntensityBarItem" HorizontalAlignment="Right" HorizontalContentAlignment="Stretch" Width="128">
<uc:IntensityBar x:Name="IntensityBarControl" ViewModel="{Binding}"/>
</StatusBarItem>
<StatusBarItem x:Name="ScaleBarItem" HorizontalAlignment="Right" HorizontalContentAlignment="Stretch" Width="100">
<uc:ScaleBar x:Name="ScaleBarControl" ViewModel="{Binding}"></uc:ScaleBar>
</StatusBarItem>
</StatusBar>
</Grid>
</UserControl>
And the code to Generate the image. For now it's a double click on the the actual UC as I yet in the "design" part of the functionality.
private void OverlayPanel_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
RenderTargetBitmap bitmap =
new RenderTargetBitmap((int)OverlayPanel.ActualWidth,
(int)OverlayPanel.ActualHeight,
96, 96, PixelFormats.Pbgra32);
bitmap.Render(OverlayPanel);
int width = bitmap.PixelWidth;
int height = bitmap.PixelHeight;
int componentsPerPixel = 4;
int totalPixels = width * height;
int totalBytes = totalPixels * componentsPerPixel;
byte[] rgbValues = new byte[totalBytes];
var pixels = new byte[bitmap.PixelWidth * bitmap.PixelHeight];
bitmap.CopyPixels(rgbValues, bitmap.PixelWidth * bitmap.Format.BitsPerPixel / 8, 0);
using (FileStream stream = File.Create(#"D:\image3.png"))
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(BitmapFrame.Create(bitmap));
encoder.Save(stream);
// If you want to export the xamDataChart to a PNG
// instead of JPEG, use this code block:
// PngBitmapEncoder encoder = new PngBitmapEncoder();
// encoder.Frames.Add(BitmapFrame.Create(bitmap));
// encoder.Save(stream);
}
}
What I am missing here? Is this the proper solution, should I try and focus on creating the image from "scratch" using the text in the component?
Two more things to mention:
uc:IntensityBar <-- it's an other image
uc:ScaleBar <-- is a grid with some lines.

Related

How to handle UserControl inside a window when the window size changed

I have a main window written in WPF that contains three sub windows and a user control with buttons. It looks like this:
What I want to do is to have the sub windows' ratio and the buttons' position fixed proportionally with the main window resizing.
I've handled the sub windows' size ratio, but I can't keep the buttons on the left side when the main widow's width is expanded:
And here is my code:
private void MainWindowResize(object sender, SizeChangedEventArgs e)
{
// sub windows size ratio
formA.Height = (this.ActualHeight - 80) * 0.5;
formA.Width = this.ActualWidth;
formB.Height = (this.ActualHeight - 80) * 0.5;
formB.Width = this.ActualWidth * 0.5;
formC.Height = (this.ActualHeight - 80) * 0.5;
formC.Width = this.ActualWidth * 0.5;
// buttons will not move to the left with this code
btnFrame.Width = this.ActualWidth;
}
+) WPF code:
MainWindow
<Grid x:Name="maingrid">
<DockPanel x:Name="panel1" LastChildFill="false">
<Frame DockPanel.Dock="Bottom" Height="35" Width="800" Source="pack://application:,,,/FormBottom;component/form_bottom.xaml" />
<WindowsFormsHost x:Name="formA" DockPanel.Dock="Top" Height="207" Width="800" >
<wftop:form_top x:Name="formTop" Dock="Fill"/>
</WindowsFormsHost>
<WindowsFormsHost x:Name="formB" DockPanel.Dock="Left" Height="207" Width="400" >
<wflt:form_left x:Name="formLeft" Dock="Fill"/>
</WindowsFormsHost>
<WindowsFormsHost x:Name="formC" DockPanel.Dock="Left" Height="207" Width="400">
<wfrt:form_right x:Name="formRight" Dock="Fill"/>
</WindowsFormsHost>
</DockPanel>
<DockPanel x:Name="panel2" LastChildFill="false" >
<Frame DockPanel.Dock="Bottom" Height="35" Width="800" Source="pack://application:,,,/FormBottom;component/form_bottom.xaml" />
</DockPanel>
</Grid>
btnFrame
<UserControl x:Class="FormBottom.form_bottom"
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:local="clr-namespace:FormBottom"
mc:Ignorable="d"
Height="35" Width="800">
<StackPanel x:Name="bottompanel" Orientation="Horizontal" Height="35" VerticalAlignment="Bottom">
<Button Content="Panel1" MinWidth="70" Click="Button_Click" />
<Button Content="Panel2" MinWidth="70" Click="Button_Click_1" Margin="10,0,0,0" />
</StackPanel>
</UserControl>
Is there a way to have the buttons fixed on the left?
You do not have size control on your own. WPF provides various Panels for layouting out-of-the-box and there are also panels for proportional layouts like Grid or DockPanel.
Your example could look like this in XAML. The Rectangles represent your views. Using Grid panel you can define rows and columns and via their RowDefinition and ColumnDefinition you can set Height and Width to either explicit sizes, e.g. 100, let the size be determined automatically to fit the content with Auto or set star-sizes like 2* which lets you define proportions. The default value is * so in the layout below, the last row sizes to its content and the other rows are sized in proportion 1:1.
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Fill="Pink"/>
<Rectangle Grid.Row="1" Grid.Column="0" Fill="MediumSeaGreen"/>
<Rectangle Grid.Row="1" Grid.Column="1" Fill="LightBlue"/>
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Orientation="Horizontal">
<Button Content="A" Width="100" Height="50"/>
<Button Content="B" Width="100" Height="50" Margin="10, 0, 0, 0"/>
</StackPanel>
</Grid>
You can achieve the same layout with different panels, so this is just an example. What is the most suitable approach depends on your requirements and preferences. The same layout in code:
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.ColumnDefinitions.Add(new ColumnDefinition());
grid.ColumnDefinitions.Add(new ColumnDefinition());
var pinkRectangle = new System.Windows.Shapes.Rectangle { Fill = Brushes.Pink };
grid.Children.Add(pinkRectangle);
Grid.SetRow(pinkRectangle, 0);
Grid.SetColumn(pinkRectangle, 0);
Grid.SetColumnSpan(pinkRectangle, 2);
var greenRectangle = new System.Windows.Shapes.Rectangle { Fill = Brushes.MediumSeaGreen };
grid.Children.Add(greenRectangle);
Grid.SetRow(greenRectangle, 1);
Grid.SetColumn(greenRectangle, 0);
var blueRectangle = new System.Windows.Shapes.Rectangle { Fill = Brushes.LightBlue };
grid.Children.Add(blueRectangle);
Grid.SetRow(blueRectangle, 1);
Grid.SetColumn(blueRectangle, 1);
var buttonA = new Button
{
Content = "A",
Width = 100,
Height = 50
};
var buttonB = new Button
{
Content = "B",
Width = 100,
Height = 50,
Margin = new Thickness(10, 0, 0, 0)
};
var stackPanel = new StackPanel { Orientation = Orientation.Horizontal };
grid.Children.Add(stackPanel);
Grid.SetRow(stackPanel, 2);
Grid.SetColumn(stackPanel, 0);
Grid.SetColumnSpan(stackPanel, 2);
stackPanel.Children.Add(buttonA);
stackPanel.Children.Add(buttonB);
I don't know what kind of control the btnFrame is but you can put the buttons inside of a e.g. StackPanel and set the HorizontalAlignment="Left" on it.
Your WPF code would be helpful to provide a better answer.
After you've posted your WPF I think you should just remove the Width="800" attribute from your Frame so that it always stretches to fit its containing DockPanel. Besides I can't see where the btnFrame name is set in the WPF.

WPF WriteableBitmap drawing over loaded picture with mouse and save. Strange cursor shift

I'm trying to implement some WPF drawing sample to understand how it works. I can solve such task with C++ very quickly but I want to understand WPF means.
During the implementation of a task I faced with some strange problem: coordinates shift of mouse cursor responsible to pixels I can see on canvas.
First of all, my task is: load some picture from file; show it on Image component; allow to draw over image with mouse (like pencil tool); save changes to new file. Task is easy to implement.
Here is my code:
XAML:
<Window x:Class="MyPaint.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:MyPaint"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Background="#FF000000"
mc:Ignorable="d"
Title="Strange Paint" Height="503.542" Width="766.281" Icon="icons/paint.png">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Column="0" Grid.Row="1" Width="Auto">
<StackPanel HorizontalAlignment="Left" Width="Auto" Background="{x:Null}">
<Button x:Name="arrowButton" Width="25" Height="25" HorizontalAlignment="Left" Click="ArrowButton_Click">
<Image Source="icons/arrow.png"/>
</Button>
<Button x:Name="selectorButton" Width="25" Height="25" Click="SelectorButton_Click" HorizontalAlignment="Left">
<Image Source="icons/select_selection_tool-128.png"/>
</Button>
<Button x:Name="clearButton" Width="25" Height="25" Click="ClearButton_Click" HorizontalAlignment="Left">
<Image Source="icons/clear.png"/>
</Button>
<Button x:Name="pencilButton" Width="25" Height="25" Click="PencilButton_Click" HorizontalAlignment="Left">
<Image Source="icons/pencil.png"/>
</Button>
<Button x:Name="fillButton" Width="25" Height="25" Click="FillButton_Click" HorizontalAlignment="Left">
<Image Source="icons/fill.png"/>
</Button>
<xctk:ColorPicker Width="50" Name="ClrPcker_Foreground" SelectedColorChanged="ClrPcker_Foreground_SelectedColorChanged">
</xctk:ColorPicker>
</StackPanel>
</Grid>
<Grid x:Name="drawingCanvas" Grid.Column="1" Grid.Row="1" MouseMove="paintImageCanvas_MouseMove" MouseLeave="PaintImageCanvas_MouseLeave" MouseLeftButtonUp="PaintImageCanvas_MouseLeftButtonUp">
<ScrollViewer Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Canvas x:Name="scrCanvas" Width="{Binding ActualWidth, ElementName=paintImageCanvas}" Height="{Binding ActualHeight, ElementName=paintImageCanvas}" >
<Image x:Name="paintImageCanvas" HorizontalAlignment="Left" VerticalAlignment="Top" Stretch="UniformToFill" MouseDown="paintImageCanvas_MouseDown" MouseMove="paintImageCanvas_MouseMove">
</Image>
<Rectangle x:Name="Rect" Stroke="DarkOrange" Visibility="Collapsed" Fill="#77EEEEEE"></Rectangle>
</Canvas>
</ScrollViewer>
</Grid>
<StackPanel Grid.Row="0">
<Menu IsMainMenu="True" DockPanel.Dock="Top" Background="#FF000000">
<MenuItem Header="_File" Foreground="White" Background="#FF000000">
<MenuItem x:Name="newMenuItem" Header="_New" Background="#FF000000" Click="NewMenuItem_Click"/>
<MenuItem x:Name="openMenuItem" Header="_Open" Click="openMenuItem_Click" Background="#FF000000"/>
<MenuItem Header="_Close" Background="#FF000000"/>
<MenuItem Header="_Save" Background="#FF000000" Click="MenuItem_Click"/>
<MenuItem x:Name="exitMenuItem" Header="_Exit" Click="exitMenuItem_Click" Background="#FF000000"/>
</MenuItem>
</Menu>
<StackPanel></StackPanel>
</StackPanel>
</Grid>
Implementation of window class:
Class members:
Point currentPoint = new Point();
ToolBoxTypes currentSelectedTool = ToolBoxTypes.Unknown;
Color foregroundColor = Brushes.Black.Color;
WriteableBitmap imageWriteableBitmap;
Constructor and initialization (init white canvas 1024x768):
public MainWindow()
{
InitializeComponent();
ClrPcker_Foreground.SelectedColor = foregroundColor;
imageWriteableBitmap = BitmapFactory.New(1024, 768);
paintImageCanvas.Source = imageWriteableBitmap;
imageWriteableBitmap.Clear(Colors.White);
int i = 0;
}
Mouse down event (here I get the first point):
private void paintImageCanvas_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ButtonState == MouseButtonState.Pressed)
{
currentPoint = e.GetPosition(paintImageCanvas);
}
if (currentSelectedTool == ToolBoxTypes.PencilTool)
{
}
}
Mouse move event (draw on canvas if pressed):
private void paintImageCanvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if(currentSelectedTool == ToolBoxTypes.PencilTool)
{
int x1 = Convert.ToInt32(currentPoint.X);
int y1 = Convert.ToInt32(currentPoint.Y);
int x2 = Convert.ToInt32(e.GetPosition(paintImageCanvas).X);
int y2 = Convert.ToInt32(e.GetPosition(paintImageCanvas).Y);
Console.WriteLine("Mouse X: " + x2 + " Mouse Y: " + y2);
imageWriteableBitmap.DrawLine( x1, y1, x2, y2, foregroundColor );
currentPoint = e.GetPosition(paintImageCanvas);
}
}
}
Ok. That's easy code.
And now, two usecases:
On start and init I can see white canvas and can draw with mouse without any problem (cursor follows pixels):
usecase 1
I loaded picture (size is 700x600) and got a problem, cursor has different place (can see a shift):
usecase 2
I think that problem is that canvas (Image) has different side than actual picture's side. I'm not sure.
Could you help me please to understand what is wrong and how to fix that?
Thanks.
Thanks to Dmitry (see comments on my question) the reason of the problem has been found: DPI of source picture.
I changed my code of picture loading and it works fine:
private void openMenuItem_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "JPEG files (*.jpg)|*.jpg|PNG files (*.png)|*.png";
if (openFileDialog.ShowDialog() == true)
{
BitmapImage image = new BitmapImage(new Uri(openFileDialog.FileName));
imageWriteableBitmap = new WriteableBitmap(image);
double dpi = 96;
int width = imageWriteableBitmap.PixelWidth;
int height = imageWriteableBitmap.PixelHeight;
int stride = width * 4;
byte[] pixelData = new byte[stride * height];
imageWriteableBitmap.CopyPixels(pixelData, stride, 0);
BitmapSource bmpSource = BitmapSource.Create(width, height, dpi, dpi, PixelFormats.Bgra32, null, pixelData, stride);
imageWriteableBitmap = new WriteableBitmap(
bmpSource.PixelWidth,
bmpSource.PixelHeight,
bmpSource.DpiX, bmpSource.DpiY,
bmpSource.Format, null);
imageWriteableBitmap.WritePixels(
new Int32Rect(0, 0, bmpSource.PixelWidth, bmpSource.PixelHeight), pixelData, stride, 0);
paintImageCanvas.Source = imageWriteableBitmap;
}
}

How to dynamically change grid rows and columns according to window size in WPF

I'm making an image gallery of sorts, and I'm currently loading the images into a grid dynamically via C#. If I shouldn't be using a grid to display the images, I'm more than willing to change it to a better-suited control, but it needs to be window-resizing-friendly.
When I resize the window, I want the following things to happen:
The images all stay the exact same size. They already fit into the grid slots while maintaining their aspect ratio
The grid spaces themselves are also not resized at all
The grid contains more or less rows/columns depending on how many can fit into the window.
This would mean that, if the window was resized to be very thin and very high, the grid would contain one column (or two, or however many are needed) and many rows to display the images in.
If it was very wide, but not high, then it would only have one (or two, or however many are needed) row(s) and many columns. Etc.
Not sure if it's needed but my code to display the images is:
for (int i = 0; i < ImageGrid.RowDefinitions.Count; i++)
{
for (int j = 0; j < ImageGrid.ColumnDefinitions.Count; j++)
{
Image img = new Image();
BitmapImage bitmap = new BitmapImage();
using (var fs = new FileStream(path, FileMode.Open)) // open the image
{
bitmap.BeginInit();
bitmap.StreamSource = fs;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
}
bitmap.Freeze(); // bitmap isn't properly cleaned up and memory leaks can occur if this isn't here
img.Source = bitmap;
img.Margin = new Thickness(10);
img.Stretch = Stretch.Uniform;
Grid.SetRow(img, i);
Grid.SetColumn(img, j);
ImageGrid.Children.Add(img);
}
}
And my XAML for the grid is:
<Grid ShowGridLines="True" Background="DimGray">
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="175" Width="0"/>
<ColumnDefinition MinWidth="755" Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Name="lblWinSize" Content="Width, Height"
HorizontalAlignment="Left" VerticalAlignment="Bottom"/>
<TextBox Name="txtnbox" Style="{StaticResource CustomTxtBox}"
TextWrapping="NoWrap" Width="Auto" Height="25" VerticalAlignment="Top"
Margin="10, 20, 10, 0"/>
<Separator Style="{StaticResource VerticalSeparator}"
HorizontalAlignment="Right" Height="Auto" Width="2" Margin="0,10,0,10"/>
<CheckBox Style="{StaticResource CustomCheckBox}"
HorizontalAlignment="Left" Margin="10,50,0,0" VerticalAlignment="Top"/>
<Grid Name="ImageGrid" Grid.Column="1" Margin="10,10,0,10"
ShowGridLines="True" Background="Gray">
<!--this is the grid where the images would go-->
</Grid>
</Grid>
Sorry about the indentation with that XAML, I couldn't get it to format right.
The rows/columns are defined elsewhere in the code, but I don't think I need to paste that considering it'll be replaced.
Again, if some other control is better suited, I can change to that.
You will want to use a wrap panel.
<WrapPanel Orientation="Horizontal">
<Image Width="50" Height="50" Source="bla.png" />
<Image Width="50" Height="50" Source="bla.png" />
<Image Width="50" Height="50" Source="bla.png" />
<Image Width="50" Height="50" Source="bla.png" />
</WrapPanel>
Use a WrapPanel to make layout of items fill the width of the window before adding a new row, inside of a ScrollViewer to allow scrolling vertically so the contents don't run out of viewable area.
<ScrollViewer>
<WrapPanel HorizontalAlignment="Center">
<!-- your items go here -->
</WrapPanel>
</ScrollViewer>
If your images are being added dynamically, hopefully you're adding them to a collection that your view can bind to instead of directly adding them to the view in your codebehind... Use an ItemsControl and define your own template for items and container:
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Images}" HorizontalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ...}" Height="150" Width="150"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
c# :
// here set number of images you want to parse
int qtyOfImages = 10;
for (int i = 0; i < qtyOfImages; i++) {
Image img = new Image();
BitmapImage bitmap = new BitmapImage();
using (var fs = new FileStream(path, FileMode.Open)) // open the image
{
bitmap.BeginInit();
bitmap.StreamSource = fs;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
}
bitmap.Freeze(); // bitmap isn't properly cleaned up and memory leaks can occur if this isn't here
img.Source = bitmap;
img.Margin = new Thickness(10);
img.Stretch = Stretch.Uniform;
//Grid.SetRow(img, i);
//Grid.SetColumn(img, j);
// set width and height so the images stay at the same size
img.Width = 300;
img.Height = 300;
ImageGrid.Children.Add(img);
}
while your xaml would be like:
<Grid ShowGridLines="True" Background="DimGray">
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="175" Width="0"/>
<ColumnDefinition MinWidth="755" Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Name="lblWinSize" Content="Width, Height"
HorizontalAlignment="Left" VerticalAlignment="Bottom"/>
<TextBox Name="txtnbox" Style="{StaticResource CustomTxtBox}"
TextWrapping="NoWrap" Width="Auto" Height="25" VerticalAlignment="Top"
Margin="10, 20, 10, 0"/>
<Separator Style="{StaticResource VerticalSeparator}"
HorizontalAlignment="Right" Height="Auto" Width="2" Margin="0,10,0,10"/>
<CheckBox Style="{StaticResource CustomCheckBox}"
HorizontalAlignment="Left" Margin="10,50,0,0" VerticalAlignment="Top"/>
<!--<Grid Name="ImageGrid" Grid.Column="1" Margin="10,10,0,10"
ShowGridLines="True" Background="Gray">
--><!--this is the grid where the images would go--><!--
</Grid>-->
<!-- set size of wrap panel to your window acutal width -->
<WrapPanel Orientation="Horizontal" Name="ImageGrid" Width="{Binding ElementName=Window, Path=ActualWidth}">
</WrapPanel>
</Grid>
dont forget to put Name="Window" at your window for this code to work
Hope that helps

Image added to canvas is bigger than the canvas

<Grid Name="PlotGrid" Margin="50,50,50,50">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="500" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Canvas Name="canvas" Grid.Row="0" Grid.Column="0" />
<Border Name="firstborder"
Grid.Column="0"
Grid.Row="0"
BorderBrush="Black"
BorderThickness="1">
</Border>
<Border Name="secondborder"
Grid.Column="1"
Grid.Row="0"
BorderBrush="Black"
BorderThickness="1">
</Border>
<Border Name="thirdborder"
Grid.Column="0"
Grid.Row="1"
BorderBrush="Black"
BorderThickness="1">
</Border>
<Border Name="fourthborder"
Grid.Column="1"
Grid.Row="1"
BorderBrush="Black"
BorderThickness="1">
</Border>
</Grid>
I am dynamically adding an image to the canvas above, which is inside a grid cell:
try
{
if ((myStream = openFileDialog.OpenFile()) != null)
{
using (myStream)
{
// Insert code to read the stream here.
image1.Source = new BitmapImage(new Uri( openFileDialog.FileName));
canvas.Children.Add(image1);
}
}
}
My expectation is that the imported image will remain inside the canvas and inside the grid cell that contains the canvas. But it occupies a huge space well beyond the grid cell. How can I keep the imported image within the confines of the grid cell?
You need to set the image size, otherwise it will be full size
image1.Stretch = Stretch.Uniform;
image1.StretchDirection = StretchDirection.Both;
image1.BeginInit();
image1.Source = new BitmapImage(new Uri( openFileDialog.FileName));
image1.Width = canvas.ActualWidth;
image1.Height = canvas.ActualHeight;
image1.EndInit();
You may also need to add code to handle resizing of the grid.
Perhaps this might work:
...
image1.Stretch = Stretch.Uniform;
image1.StretchDirection = StretchDirection.Both;
image1.BeginInit();
image1.Source = new BitmapImage(new Uri( openFileDialog.FileName));
image1.EndInit();
canvas.Children.Add(image1);

Rendering images fails when image over 40,000 wide

I am trying to create a time scale like in Adobe Premiere like this:
But, I have to go to down to 0.01 second increments.
My timeline control looks like:
UPDATE:
I have used #Sten Petrov suggestion and used a VisualBrush.
But now I am stuck on how to implement the Label for the Seconds.
My new code (containing control can change):
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="680.839">
<Grid Background="Black">
<ScrollViewer VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Visible" >
<ScrollViewer.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid SnapsToDevicePixels="False" UseLayoutRounding="True">
<Grid.Background>
<VisualBrush TileMode="Tile" Viewport="0,0,5,30" ViewportUnits="Absolute" Viewbox="0,0,5,30" ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Line Stroke="Coral" StrokeThickness="2" X1="0" X2="0" Y1="25" Y2="30" UseLayoutRounding="True" />
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
</Grid>
<Grid Margin="50,0,0,0" SnapsToDevicePixels="False" UseLayoutRounding="True">
<Grid.Background>
<VisualBrush TileMode="Tile" Viewport="0,0,50,30" ViewportUnits="Absolute" Viewbox="0,0,50,30" ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Line Stroke="Red" StrokeThickness="2" X1="0" X2="0" Y1="20" Y2="30" UseLayoutRounding="True" />
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
</Grid>
<Grid SnapsToDevicePixels="False" Height="30" UseLayoutRounding="True" >
<Grid.Background>
<VisualBrush TileMode="Tile" Viewport="0,0,500,30" ViewportUnits="Absolute" Viewbox="0,0,500,30" ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Grid HorizontalAlignment="Left" Width="500" UseLayoutRounding="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="21*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="20*"/>
<ColumnDefinition Width="9*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="1" Height="9" Content=".100" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="2" Height="9" Content=".200" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="3" Height="9" Content=".300" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="4" Height="9" Content=".400" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="5" Height="9" Content=".500" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="6" Height="9" Content=".600" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="7" Height="9" Content=".700" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="8" Height="9" Content=".800" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="9" Height="9" Content=".900" Foreground="White"/>
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
</Grid>
<Grid SnapsToDevicePixels="False" Height="30" UseLayoutRounding="True" Margin="500,0,0,0" >
<Grid.Background>
<VisualBrush TileMode="Tile" Viewport="0,0,500,30" ViewportUnits="Absolute" Viewbox="0,0,500,30" ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Line Stroke="Blue" StrokeThickness="2" X1="0" X2="0" Y1="10" Y2="30" UseLayoutRounding="True" />
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
</Grid>
<Grid SnapsToDevicePixels="False" Height="30" UseLayoutRounding="True" Margin="491,0,0,0" >
<Grid.RowDefinitions>
<RowDefinition Height="7"/>
<RowDefinition Height="23"/>
</Grid.RowDefinitions>
<!--Need something here-->
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="0" Height="9" Content="00:00" Foreground="White"/>
</Grid>
</Grid>
</DataTemplate>
</ScrollViewer.ContentTemplate>
</ScrollViewer>
</Grid>
/UPDATE
I go to 0.01 seconds per line, so for a 10 minute timeline I am looking at drawing 60000 lines + 6000 labels.
I asked a prior question on this before: 10000's+ UI elements, bind or draw?
Originally I was drawing lines directly on a Canvas.
Then I went to using a VisualHost because it is supposed to be lighter weight.
Well it isn't light enough.
I have a MediaElement that plays a video and the Timeline scrolls in sync with the video position. A ScrollViewer wraps my Timeline and does .ScrollToHorizontalOffset about every 10ms.
If my Timeline was over something like 3 minutes the video shutters.
I assume this is because a VisualHost still has all the Framework Elements in it and the scrolling causes them to be re-validated.
So now I am trying to generate a Image to display, I think that should be lighter yet.
Am I wrong in this assumption?
Now I am facing issues with making the Timeline into an Image.
I could not Render an entire Timeline to a Image so I am 'Chunking' it. I was hitting Exceptions about Image size being to big.
On to my code:
This is my main entry point.
public void RenderHeaderPicture()
{
const int ChunkSize = 5000;
var bitmapFrames = new List<BitmapFrame>();
// generates X number of DrawingVisual's based on ChunkSize
List<DrawingVisual> visuals = generateHeaderVisualChunks(
AppViewModel.TimelineViewModel.HeaderWidth, ChunkSize, TimelineViewModel.ViewLevel.Level1);
for (var i = 0; i < visuals.Count; i++)
{
var renderTargetBitmap = new RenderTargetBitmap(ChunkSize, 30, 96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(visuals[i]);
//test to make sure image good
saveHeaderSegmentAsPng(string.Format("headerSeg{0}.png", i), renderTargetBitmap);
bitmapFrames.Add(BitmapFrame.Create(renderTargetBitmap));
}
// put the frames back together now
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
for (int i = 0; i < bitmapFrames.Count; i++)
{
drawingContext.DrawImage(bitmapFrames[i], new Rect(i * ChunkSize, 0, bitmapFrames[i].PixelWidth, 30));
}
drawingContext.Close();
}
var newBmp = new RenderTargetBitmap(AppViewModel.TimelineViewModel.HeaderWidth, 30, 96, 96, PixelFormats.Pbgra32);
newBmp.Render(drawingVisual);
AppViewModel.TimelineViewModel.HeaderImageSource = newBmp;
}
Here is the code that creates the DrawingVisual's
private List<DrawingVisual> generateHeaderVisualChunks(int width, int chunkSize, TimelineViewModel.ViewLevel level)
{
var ret = new List<DrawingVisual>();
var currentTime = new TimeSpan(0, 0, 0, 0, 0);
var timeStep = new TimeSpan(0, 0, 0, 0, (int)level);
var currentLine = 0;
const double DistanceBetweenLines = 5;
const int TenthOfSecondLine = 10;
const int SecondLine = 100;
int iterations = (width / chunkSize);
int remainder = width % chunkSize; //not doing anything with yet
var grayBrush = new SolidColorBrush(Color.FromRgb(192, 192, 192));
var grayPen = new Pen(grayBrush, 2);
var whitePen = new Pen(Brushes.Purple, 2);
grayBrush.Freeze();
grayPen.Freeze();
whitePen.Freeze();
for (int i = 0; i < iterations; i++)
{
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
double currentX = 0;
if (i > 0)
{
currentLine--;
currentTime -= timeStep;
}
while (currentX <= chunkSize)
{
if (((currentLine % SecondLine) == 0) && currentLine != 0)
{
dc.DrawLine(whitePen, new Point(currentX, 30), new Point(currentX, 15));
FormattedText text = null;
double tempX = currentX;
switch (level)
{
case TimelineViewModel.ViewLevel.Level1:
text = new FormattedText(
currentTime.ToString(#"hh\:mm\:ss\.fff"),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Tahoma"),
8,
grayBrush);
break;
}
dc.DrawText(text, new Point((tempX - 22), 0));
}
else if ((((currentLine % TenthOfSecondLine) == 0) && currentLine != 0)
&& (currentLine % SecondLine) != 0)
{
dc.DrawLine(grayPen, new Point(currentX, 30), new Point(currentX, 20));
FormattedText text = null;
switch (level)
{
case TimelineViewModel.ViewLevel.Level1:
text = new FormattedText(
string.Format(".{0}", currentTime.Milliseconds),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Tahoma"),
8,
grayBrush);
break;
}
dc.DrawText(text, new Point((currentX - 8), 8));
}
else
{
dc.DrawLine(grayPen, new Point(currentX, 30), new Point(currentX, 25));
}
currentX += DistanceBetweenLines;
currentLine++;
currentTime += timeStep;
}
}
ret.Add(visual);
}
return ret;
}
Save png segment:
private static void saveHeaderSegmentAsPng(string fileName, RenderTargetBitmap renderTargetBitmap)
{
var pngBitmapEncoder = new PngBitmapEncoder();
pngBitmapEncoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
using (var fileStream = new FileStream(fileName, FileMode.Create))
{
pngBitmapEncoder.Save(fileStream);
fileStream.Flush();
fileStream.Close();
}
}
All of my png segments are rendered correctly in their separate files.
And my Timeline is rendered correctly until I go over 1:20 then things break.
See:
It's like the Image is smeared or something.
Anyone know what is going on with this?
Thanks
I'm waiting for the day when my living room will have a TV with horizontal resolution that will require something like your approach.
Scrap this whole piece of code you just demoed here, it will never become usable and maintainable, you could only manage to squeeze one of these out of it.
Then learn about VisualBrush, there are plenty of tutorials out there, it can repeat your visual template, no need of PNGs and it will scale better when screen resolution changes (up to 40001px wide)
For the numbers that appear above the marks there are a million different approaches, some of them can be combined with the visual brush mentioned above, such as a user control that represents your timeline unit (the space between two larger marks). Now place several of them in a grid (stackpanel, canvas... as you wish) and adjust (dynamically) their offset and labels - all of a sudden you can represent an infinite timeline with 10 controls on the screen.

Categories