Background:
I'm loading images into a Stack Panel (called MainStack) displayed horizontally (for argument sake, 10 images) with only room for 4 images in view. As I load the images from the List I'm setting the width of each to 300 so they're all in the same size box.
I want to move the images from right to left using the Stack Panel's Margin (left) property. I want the appearance of scrolling left by exactly the same amount of the width of each image (looped with 4 second delay) until the last image is in view. Here's my code for the Margin animation:
Dim result As New Storyboard
Dim animation As New ThicknessAnimation
animation.From = MainStack.Margin
animation.EasingFunction = New PowerEase() With {.EasingMode = EasingMode.EaseInOut, .Power = 3}
animation.To = New Thickness(-300, 0, 0, 0)
animation.Duration = New Duration(TimeSpan.FromSeconds(1.5))
Storyboard.SetTarget(animation, MainStack)
Storyboard.SetTargetProperty(animation, New PropertyPath("Margin"))
result.Children.Add(animation)
result.Begin()
Strange thing is happening. The Stack Panel is moving to the left but only by about half the width of the image.
What is going on?!?
/* edit */
As per H.B. suggestion, I've tried to implement a TranslateTransform but not having much success.
Can anyone see any problems with this code?
Dim translatePosition = New Point(300, 0)
RenderTransform = New TranslateTransform()
Dim d As New Duration(New TimeSpan(0, 0, 0, 1, 30))
Dim x As New DoubleAnimation(translatePosition.X, d)
Storyboard.SetTarget(x, MainStack)
Storyboard.SetTargetProperty(x, New PropertyPath("(UIElement.RenderTransform).(TranslateTransform.X)"))
Dim sb As New Storyboard()
sb.Children.Add(x)
sb.Begin()
Nothing seems to be happening.
Ben
I think you should try putting your entire stackpanel in a canvas and just animating the Canvas.Left property to scroll the images.
Another option for you is to use a horizontal ListBox, then you can animate the ScrollViewer. If you want to try it this way, here's a link that may help: WPF - Animate ListBox.ScrollViewer.HorizontalOffset?.
Related
I'm using a DataGrid to represent some data in a WPF application. In a feature where I'm saving a particular WPF Window which has the DataGrid into a PDF using PDFSharp, I'm facing an issue that the DataGrid GridLines are not visible when the saved PDF is viewed in smaller viewing percentages.
(Refer attached images, only when the PDF view is set at 139%, the GridLines are visible. However, in smaller viewing %, some grid lines get omitted.)
Here's the PDF Saving Code:-
MemoryStream lMemoryStream = new MemoryStream();
Package package = Package.Open(lMemoryStream, FileMode.Create);
var doc = new System.Windows.Xps.Packaging.XpsDocument(package);
XpsDocumentWriter writer = System.Windows.Xps.Packaging.XpsDocument.CreateXpsDocumentWriter(doc);
VisualBrush sourceBrush = new VisualBrush(this);
DrawingVisual drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(this.ActualWidth, this.ActualHeight)));
}
writer.Write(drawingVisual);
doc.Close();
package.Close();
var pdfXpsDoc = PdfSharp.Xps.XpsModel.XpsDocument.Open(lMemoryStream);
XpsConverter.Convert(pdfXpsDoc, sFileName, 0);
I believe it has to do with the quality with which the visual is drawn. Then I tried this snippet where I'm using DrawImage to make the visual at a higher resolution. Here's the snippet:-
MemoryStream lMemoryStream = new MemoryStream();
Package package = Package.Open(lMemoryStream, FileMode.Create);
var doc = new System.Windows.Xps.Packaging.XpsDocument(package);
XpsDocumentWriter writer = System.Windows.Xps.Packaging.XpsDocument.CreateXpsDocumentWriter(doc);
double dpiScale = 600.0 / 96.0;
var renderBitmap = new RenderTargetBitmap(Convert.ToInt32(this.Width * dpiScale),
Convert.ToInt32(this.Height * dpiScale),
600.0,
600.0,
PixelFormats.Pbgra32);
renderBitmap.Render(this);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawImage(renderBitmap, new Rect(0, 0, this.Width, this.Height));
}
writer.Write(visual);
doc.Close();
package.Close();
var pdfXpsDoc = PdfSharp.Xps.XpsModel.XpsDocument.Open(lMemoryStream);
XpsConverter.Convert(pdfXpsDoc, _pdfFileName, 0);
This snippet is working as in the grid lines are visible even in smaller viewing percentages but it makes my application stuck at the PDF save operation and also it throws System.OutofMemoryException with message "Insufficient memory to continue the execution of the program." However, the application doesn't crash.
To check the behavior of PDF viewer, I generated a table with multiple rows and columns in MS Word and saved it as a PDF. In that case, the table grid lines are clearly visible even at small viewing percentages.
Can anyone help me with this?
I assume the first code snippet creates a table in vector format (you do not supply a PDF that allows to verify this).
The second code snippet attempts to create a bitmap image (raster format).
Either way: with both vector and raster images it depends on the PDF viewer whether thin lines are visible. Adobe Reader has many options (like "Enhance thin lines", "Smooth line art", "Smooth images") that will have an effect on the actual display - to be set on the client computer, nothing to be set in the PDF.
I assume your test with MS Word also created a table in vector format, but maybe with thicker lines. So this test proofs nothing.
I had the same problem with disappearing grid lines when zooming out a PDF created with WPF.
The problem was that the TextBox objects in the Grid cells had a default background color (white) and a border color (black), and both were painted in the same place when zooming out. The solution was to not have a background at all, by setting the background to Transparent.
TextBox tx = new TextBox();
tx.Text = "X";
tx.SetValue(Grid.RowProperty, row);
tx.SetValue(Grid.ColumnProperty, col);
tx.BorderThickness = new Thickness(0.3, 0.3, 0, 0);
tx.BorderBrush = System.Windows.Media.Brushes.Black;
tx.Background = Brushes.Transparent;
grid.Children.Add(tx);
But what if you want to have some background in the grid cell? Then the solution is to add a separate Border object to the same Grid cell, and use Zindex to make sure that the Border object is painted in front of the other content.
TextBox tx = new TextBox();
tx.Text = "X";
tx.SetValue(Grid.RowProperty, row);
tx.SetValue(Grid.ColumnProperty, col);
tx.BorderThickness = new Thickness(0);
tx.Background = Brushes.LightPink;
grid.Children.Add(tx);
Border ct = new Border();
ct.SetValue(Grid.RowProperty, row);
ct.SetValue(Grid.ColumnProperty, col);
ct.BorderThickness = new Thickness(0.3, 0.3, 0, 0);
ct.BorderBrush = System.Windows.Media.Brushes.Black;
ct.Background = Brushes.Transparent;
ct.HorizontalAlignment = HorizontalAlignment.Stretch;
ct.VerticalAlignment = VerticalAlignment.Stretch;
Grid.SetZIndex(ct, 100);
grid.Children.Add(ct);
Also, UseLayoutRounding must be set to false (false is default). Otherwise lines with Thickness 0.5 or lower will disappear completely.
How to determine the Anchor Point of the System.Windows.Media.DrawingContext? exactly like RenderTransformOrigin in Image WPF Control.
Dim InImage As New BitmapImage(New Uri("Image Path"))
Dim DrawingGroup As New DrawingGroup
Dim DrawingContext As DrawingContext = DrawingGroup.Open
DrawingContext.PushTransform(New RotateTransform(53))
DrawingContext.DrawImage(InImage, New Rect(0, 0, 500, 500))
DrawingContext.Close()
I want to Render Image using Several Anchor Points.
I think you mean Anchor Point as the point around which the rotating is performed. So you can set that point right via the RotateTransform. It has a pair of properties namely CenterX and CenterY:
...
Dim Rotating As New RotateTransform(53)
Rotating.CenterX = Some_Value_For_X
Rotating.CenterY = Some_Value_For_Y
DrawingContext.PushTransform(Rotating)
...
I'm trying to take a screenshot of a datagrid which has to many rows to be displayed. So there is a scrollviewer.
So when I just put the datagrid into the Render Method of RenderTargetBitmap I obviously just get the viewable part of the datagrid.
I read that one can take a screenshot of a content when actually rendering the ItemsPresenter of the ScrollViewer of that control, as the ItemsPresenter would have the "real" Width and Height of the content.
Unfortunatly my ScrollViewer doesnt have any different Height, ActualHeight or RenderSize.Height than the dataGrid.
So I always just get the visible part of the Content.
Anyone know how to do this the right way, that it actually takes the whole content ?
Code:
var scroll = GetTemplateChildByName(dataGridInOut);
if (scroll != null)
{
var item = scroll.Content as ItemsPresenter;
var width = item.RenderSize.Width;
var height = item.RenderSize.Height;
var rtb = new RenderTargetBitmap((int) Math.Round(width), (int)Math.Round(height), 96, 96,
PixelFormats.Pbgra32);
var drawingVisual = new DrawingVisual();
var visualBrush = new VisualBrush(item);
using (var context = drawingVisual.RenderOpen())
{
context.DrawRectangle(visualBrush, null, new Rect(new Point(0,0), new Size(width, height)));
}
rtb.Render(drawingVisual);
Clipboard.SetImage(rtb);
}
Leaf is right. You could instantiate another DataGrid bound to the same source programmatically, put it into a container which gives it infinite space, wait for it to render and then take a screenshot of this. No need to actually show it in the UI.
I'm working on a 3D carousel of flat, square tiles that will contain information. I'm working on animating this carousel to rotate when a person presses Next and Previous buttons.
I've gotten it to work by using BeginAnimation on the Rotation property of the RotateTransform3D I applied to the carousel, but I can't seem to make a Storyboard version of the same animation work. The reason I need the Storyboard version is for the HandOffBehavior.Compose parameter because without it, multiple clicks of my next and previous buttons results in a misaligned carousel.
Here is the code for the Storyboard:
RotateTransform3D tempTransform = (RotateTransform3D)wheel.Transform;
AxisAngleRotation3D rotation = (AxisAngleRotation3D)tempTransform.Rotation;
Storyboard storyboard = new Storyboard();
DoubleAnimation animation = new DoubleAnimation();
animation.By = defaultAngle;
animation.Duration = TimeSpan.FromSeconds(.5);
Storyboard.SetTarget(animation, rotation);
Storyboard.SetTargetProperty(animation, new PropertyPath("Angle"));
storyboard.Children.Add(animation);
storyboard.Duration = animation.Duration;
storyboard.Begin(new FrameworkContentElement(), HandoffBehavior.Compose);
For some reason, this code results in absolutely nothing. I followed the examples I had to the letter, so I am quite frustrated. Any help is greatly appreciated. I am also completely open to using BeginAnimation if I can replicate HandOffBehavior.Compose.
My experience comes from 2D animation, but I guess the problem is the same.
For some stupid reason (probably relating to an unhealthy focus on XAML), Storyboards can only animate Freezable objects by looking them up by name. (See example in Storyboards Overview.) Thus although you provide a reference to your 'rotation' object when you call Storyboard.SetTarget(animation, rotation), the Storyboard only wants to remember and use a name, which it does not have.
The solution is:
Create a naming scope around the element that will govern the transform.
Call RegisterName() for each Freezable object being animated.
Pass the element to Storyboard.Begin()
Which would make your code look something like this (not tested):
FrameworkContentElement element = new FrameworkContentElement();
NameScope.SetNameScope(element, new NameScope());
RotateTransform3D tempTransform = (RotateTransform3D)wheel.Transform;
AxisAngleRotation3D rotation = (AxisAngleRotation3D)tempTransform.Rotation;
element.RegisterName("rotation", rotation);
Storyboard storyboard = new Storyboard();
DoubleAnimation animation = new DoubleAnimation();
animation.By = defaultAngle;
animation.Duration = TimeSpan.FromSeconds(.5);
Storyboard.SetTarget(animation, rotation);
Storyboard.SetTargetProperty(animation, new PropertyPath("Angle"));
storyboard.Children.Add(animation);
storyboard.Duration = animation.Duration;
storyboard.Begin(element, HandoffBehavior.Compose);
None of this is necessary in XAML because your objects are automatically registered.
EDIT: But then I worked out that you can simplify things by leaving out the Storyboard altogether:
var T = new TranslateTransform(40, 0);
Duration duration = new Duration(new TimeSpan(0, 0, 0, 1, 0);
DoubleAnimation anim = new DoubleAnimation(30, duration);
T.BeginAnimation(TranslateTransform.YProperty, anim);
Im trying to create some chart images without ever displaying those charts on the screen. I'v been at this for quite a while and tried a lot of different things but nothing seems to work. The code works perfectly if I display the chart in a window first, but if I don't display it in a window, the bitmap is just white with a black border (no idea why).
I have tried adding the chart to a border before rendering and giving the border a green borderBrush. In the bitmap, I see the green borderBrush then the black border and white background but no chart. The Chart is not contained in a black boarder so I don't know where that is coming from.
I have tried adding the chart to a window without calling window.Show() and again I just get the black boarder and white background. However if I call window.Show() the bitmap contains the chart.
I have tried using a drawingVisual as explained here, same result.
Here is the code (not including adding the element to a border or window):
private static BitmapSource CreateElementScreenshot(FrameworkElement element, int dpi)
{
if (!element.IsMeasureValid)
{
Size size = new Size(element.Width, element.Height);
element.Measure(size);
element.Arrange(new Rect(size));
}
element.UpdateLayout();
var scale = dpi/96.0;
var renderTargetBitmap = new RenderTargetBitmap
(
(int)(scale * element.RenderSize.Width),(int)(scale * element.RenderSize.Height),dpi,dpi,PixelFormats.Default
);
// this is waiting for dispatcher to perform measure, arrange and render passes
element.Dispatcher.Invoke(((Action)(() => renderTargetBitmap.Render(element))), DispatcherPriority.Render);
return renderTargetBitmap;
}
Note: The chart is a ContentControl.
Is there anyway I can get the chart to render without displaying it in a window first?
Calling element.ApplyTemplate() did the trick.
If someone has similar problems with rendering RenderTargetBitmap (getting white / empty image) items that are in StackPanel you can temporary move them to Grid, then render and put it back in StackPanel
Grid grid = new System.Windows.Controls.Grid() { Background = Brushes.White, Width = iWidth, Height = iHeight };
Panel panel = plot.Parent as Panel;
if (panel != null)
{
panel.Children.Remove(plot);
grid.Children.Add(plot);
grid.Measure(new Size(iWidth, iHeight));
grid.Arrange(new Rect(new Size(iWidth, iHeight)));
}
plot.Measure(new Size(iWidth, iHeight));
plot.Arrange(new Rect(new Size(iWidth, iHeight)));
plot.ApplyTemplate();
plot.UpdateLayout();
grid.ApplyTemplate();
grid.UpdateLayout();
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(
iWidth,
iHeight,
96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(grid);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
MemoryStream memoryStream = new MemoryStream();
encoder.Save(memoryStream);
bitmap = new System.Drawing.Bitmap(memoryStream);
if (panel != null)
{
grid.Children.Remove(plot);
panel.Children.Add(plot);
}
plot.Measure(new Size(iWidthBefore, iHeightBefore));
plot.Arrange(new Rect(new Size(iWidthBefore, iHeightBefore)));
plot.UpdateLayout();
For me, calling element.Arrange() was the missing piece.