Losing my mind here. I spent the last couple days writing the beginnings of a windows app in WinForms... yesterday did a 90 degree turn and had to switch over to WPF for aesthetic reasons. Been having some trouble converting certain things over... namely anything graphics related. In the wpf version, I had the following C# code to created a black circle and white rectangle at the point where the user clicked on the panel and display a message box displaying what the x coordinate is:
Graphics g;
private void panel1_MouseClick(object sender, MouseEventArgs e)
{
SolidBrush s2 = new SolidBrush(Color.Black);
g.FillRectangle(s2, e.X + 3, e.Y + 3, 10, 10);
SolidBrush s = new SolidBrush(Color.White);
g.FillPie(s, e.X + 4, e.Y + 4, 7, 7, 0, 360);
float x_cord = e.X;
MessageBox.Show("X is: " + x_cord.ToString());
}
I tried to do this in wfb via the following code:
Graphics g;
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
System.Windows.Point position = e.GetPosition(this);
double pX = position.X;
double pXint = Convert.ToInt32(pX);
MessageBox.Show(pX.ToString());
SolidBrush s2 = new SolidBrush(System.Drawing.Color.Black);
g.FillRectangle(s2, pXint, 5, 10, 10);
}
This compiles and runs correctly but when I click on canvas, nothing happens! The messagebox was there mainly as a test to see if the event was triggering at all, which it clearly isn't. Here's the corresponding XML for the window with the canvas in it:
<Grid>
<Canvas Name="Surface" Height="Auto" Width="Auto" Background="White"> </Canvas>
</Grid>
What am I doing wrong here? I actually like WPF better for the most part but certain things are an absolute nuisance.
The problem is you are trying to Draw System.Drawing objects onto a WPF Canvas, you cant do that.
One way to solve this is to just add Rectangles to you Canvas.
<Grid>
<Canvas Name="Surface" Height="Auto" Width="Auto" Background="White" MouseLeftButtonDown="Surface_MouseLeftButtonDown"></Canvas>
</Grid>
private void Surface_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Rectangle rect = new Rectangle { Width = 10, Height = 10, Fill = Brushes.Black };
Surface.Children.Add(rect);
Canvas.SetLeft(rect, e.GetPosition(this).X);
Canvas.SetTop(rect, e.GetPosition(this).Y);
}
Got it. Can't believe it took me 6 hours to draw a Rectangle. Not exactly sure what made it work but think it had to do somewhat with getting rid of using System.Drawing; When I tried sa_ddam213's code I got an error saying "Cannot convert from System.Drawing.Rectangle to System.Windows.UIElement" for unknown reasons. After google search number 5007 I came upon this article:
http://msdn.microsoft.com/en-us/library/ms747393.aspx
I had looked at it before but couldn't get the code to compile for the line or the elipse, I'm thinking it was System.Drawing assembly causing problems.
Thanks so much for the help all!!
Related
Simple problem.
I have a form to which I add a panel and put 1 label with some text. When I look at the saved image, all I see is the panel. I have tried all the solutions I could find. Code below. I get the panel saved but the text box doesn't appear. If I can get that to work, then I can do all that I need.
What am I doing wrong?
int x = SystemInformation.WorkingArea.X;
int y = SystemInformation.WorkingArea.Y;
int width = printPanel.Width;
int height = printPanel.Height;
Rectangle bounds = new Rectangle(x, y, width, height);
using (Bitmap flag = new Bitmap(width, height))
{
printPanel.DrawToBitmap(flag, bounds);
if (Environment.UserName == "grimesr")
{
string saveImage = Path.Combine(fileStore, "./p" + ".png");
flag.Save(saveImage);
}
}
Really not sure where you're going wrong.
Here's a simple test:
private void button1_Click(object sender, EventArgs e)
{
int width = printPanel.Width;
int height = printPanel.Height;
Rectangle bounds = new Rectangle(0, 0, width, height);
Bitmap flag = new Bitmap(width, height);
printPanel.DrawToBitmap(flag, bounds);
pictureBox1.Image = flag;
}
It grabs the entire panel and puts the image into the picturebox to the right:
Thanks for the hints, even if they weren't directly related. The print button helped me figure this out. The button code worked as desired. However, putting the same code where I had it wasn't working. I then noticed a InvalidOperation error was being rasied. So looking at more in detail led me to see what the real issue was.
I must admit I left out 1 tiny piece of information that was critical. I was trying to do this in a thread that was feeding my label printer. Of course, trying to used a UI panel control in the thread threw an Invalid Operation. I moved the code out and all is well now. Cross thread operations are sometimes subtle and difficult to think about how they fail.
Problem solved for me.
This is the problem (unecessary margin showed by the red arrow):
This is the actual XAML of it:
<Ribbon DockPanel.Dock="Top">
This is the patch (which appears to me as working but a hack instead of a real solution):
<Ribbon DockPanel.Dock="Top" Margin="0, -22, 0, 0">
With the patch (more a hack than anything else to me):
Why there is a margin (border/space) at the top of the Ribbon and how to remove that margin properly without a hack (Margin -22 is a hack to me)?
Solution applied (Ed Bayiates solution):
<Ribbon DockPanel.Dock="Top" x:Name="MyRibbon" SizeChanged="RibbonSizeChanged">
private void RibbonSizeChanged(object sender, SizeChangedEventArgs e)
{
ContentPresenter titlePanel = MyRibbon.Template.FindName("PART_TitleHost", MyRibbon) as ContentPresenter;
if (titlePanel != null)
{
double titleHeight = titlePanel.ActualHeight;
MyRibbon.Margin = new Thickness(MyRibbon.Margin.Left, -titleHeight, MyRibbon.Margin.Right, MyRibbon.Margin.Bottom);
}
}
I think the area in question collapses into the Window titlebar if you host in a RibbonWindow instead of a standard Window.
If you can't do that, there are three items that take the same 22 pixel space in that area. One is PART_TitleHost. The second is a DockPanel with no Name attribute and the third is a Border with no Name attribute. Unless you re-template the whole Ribbon I don't think you can easily get rid of these. However, you could make your hack a bit less hacky if you set the y-margin to the exact size of this area. In the codebehind you can get the titlebar's actual height and reset the margin of the ribbonbar:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
ContentPresenter titlePanel = Ribbon.Template.FindName("PART_TitleHost", Ribbon) as ContentPresenter;
if (titlePanel != null)
{
double titleHeight = titlePanel.ActualHeight;
Ribbon.Margin = new Thickness(Ribbon.Margin.Left, -titleHeight, Ribbon.Margin.Right, Ribbon.Margin.Bottom);
}
}
Image without that code:
Image with that code:
I cannot figure out how to move a WPF shape at runtime. Specifically, I want to move a ellipse.
Here is my current code:
private void Tick(object sender, EventArgs e)
{
Point ballLocation = ball.TransformToAncestor(Application.Current.MainWindow).Transform(new Point(0, 0));
//MessageBox.Show(ballLocation.ToString());
Canvas.SetLeft(ball, ballLocation.X + 5);
InvalidateVisual();
}
Every time the timer ticks (1 second) the ball should move 5 pixels in the x direction, correct? If this is wrong, how do I get the current location of the Ellipse and how do I set it to a new location. Maybe there is a problem with the InvalidateVisual? I believe that basically repaints the control. If that is wrong, how do I repaint the ellipse to show its change in location. I am also tried ball.InvalidateVisual(), it did not work.
This is how I create and start the timer:
var timer = new DispatcherTimer {IsEnabled = true};
timer.Tick += Tick;
timer.Interval = new TimeSpan(0, 0, 1);
timer.Start();
If Canvas.Left is initialized with some value, either in XAML
<Ellipse Name="ball" Canvas.Left="0" ... />
or in code
Canvas.SetLeft(ball, 0);
the following works:
void Tick(object sender, EventArgs e)
{
Canvas.SetLeft(ball, Canvas.GetLeft(ball) + 5);
}
The initialization is necessary because the default value is Double.NaN.
You may however consider to animate the Canvas.Left property to get a smooth movement. Start reading here: Animation Overview
I am rendering a WPF Visual (UserControl) to a bitmap but the problem is that the rendered image is the size of the UserControl before it is scaled/transformed. So let's say the UserControl was designed at 200x200 pixels. When I render to BMP I'm using the UserControl's ActualWidth and ActualHeightt which report 200 and 200 respectively. Problem is the UserControl is in a Canvas and is auto sized (set to scale/fill with the Window size) to something closer to 1200 x 1200 (it changes)
I've done some reading and searching and so far can't figure out how to determine the effective size, that is the size the control is being painted on screen.
I came across this question which sounded hopeful but the Transform returned does not contain scaling data. Well it does, but they are both 1.
Get element position after transform
Any suggestions on where to look for the render scaling would be great!
[UPDATE] As suggested, I'm including the relevant code:
public static Bitmap PngBitmap(this Visual visual)
{
// Get height and width
int width = (int)(double)visual.GetValue(
FrameworkElement.ActualWidthProperty);
int height = (int)(double)visual.GetValue(
FrameworkElement.ActualHeightProperty);
// Render
RenderTargetBitmap rtb =
new RenderTargetBitmap(
width,
height,
96,
96,
PixelFormats.Default);
rtb.Render(visual);
// Encode
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
System.IO.MemoryStream stream = new System.IO.MemoryStream();
encoder.Save(stream);
// Create Bitmap
Bitmap bmp = new Bitmap(stream);
stream.Close();
return bmp;
}
public static BitmapSource BitmapSource(this Visual visual)
{
Bitmap bmp = visual.PngBitmap();
IntPtr hBitmap = bmp.GetHbitmap();
BitmapSizeOptions sizeOptions = BitmapSizeOptions.FromEmptyOptions();
return Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
Int32Rect.Empty,
sizeOptions);
}
[Update #2] Added the XAML - The Grid element was removed because it was HUGE and from my reading of the XAML the Canvas containing the keyboard UserControl was NOT part of the Grid element.
<UserControl 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:PMD.HECAT.DashboardModule"
xmlns:PMD_HECAT_DashboardModule_VirtualKeyboard="clr-namespace:PMD.HECAT.DashboardModule.VirtualKeyboard"
xmlns:System_Windows_Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit"
xmlns:PMD_HECAT_DashboardModule_Input="clr-namespace:PMD.HECAT.DashboardModule.Input"
xmlns:control="clr-namespace:PMD.HECAT.DashboardModule.Controls"
x:Class="PMD.HECAT.DashboardModule.CandidateElectrodeView"
x:Name="UserControl"
mc:Ignorable="d"
d:DesignWidth="1024" d:DesignHeight="768" Width="640" Height="360">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Themes/DashboardStyles.xaml" />
<ResourceDictionary Source="../Themes/ImageButtons.xaml" />
<ResourceDictionary Source="CandidateViewResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="PMD_HECAT_DashboardModule_VirtualKeyboard:VirtualKeyboardView.KeyboardClose" SourceName="virtualKeyboardView">
<BeginStoryboard Storyboard="{StaticResource OnKeyboardClose1}"/>
</EventTrigger>
</UserControl.Triggers>
<Canvas Width="100" HorizontalAlignment="Left">
<PMD_HECAT_DashboardModule_VirtualKeyboard:VirtualKeyboardView x:Name="virtualKeyboardView" Height="222" Width="550" RenderTransformOrigin="0.5,0.5" Opacity="0" Active="False">
<PMD_HECAT_DashboardModule_VirtualKeyboard:VirtualKeyboardView.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform X="40" Y="400"/>
</TransformGroup>
</PMD_HECAT_DashboardModule_VirtualKeyboard:VirtualKeyboardView.RenderTransform>
</PMD_HECAT_DashboardModule_VirtualKeyboard:VirtualKeyboardView>
<Rectangle Stroke="White" Opacity="0.7" Fill="White" Height="370" Width="654.851" Canvas.Left="687" Canvas.Top="0" />
</Canvas>
</UserControl>
I know lots of time passed since the question was asked but it doesn't hurt to post some more info :)
I use this code to find size (scale) of visuals with applied render transform(s).
GeneralTransform t = transformedVisual.TransformToVisual(parentVisual);
Vector topLeft = (Vector) t.Transform(new Point(0,0));
Vector topRight = (Vector) t.Transform(new Point(originalWidthOfTransformedVisual, 0));
double renderedWidth = (topRight-topLeft).Length;
double widthScale = renderedWidth / originalWidthOfTransformedVisual;
Hi i had a similar problem if the visual wasn't displayed before to force this rendering
uiElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
uiElement.Arrange(new Rect(new Point(0, 0), uiElement.DesiredSize));
Size _size = uiElement.DesiredSize;
this works at least in my case to fore the UIElement to Render it Size
If there is a RenderTransform you will hardly be able to determine the size unless you you some serious calculations
In most other cases you will be able to get the size of the FrameworkElement by using the ActualHeight and ActualWidth properties. Still, these properties will give you the size of the bounding box which will be sufficient for the scenario you describe.
You say you use the Visual's width and height.
First, there are two problems.
A Visual, http://msdn.microsoft.com/en-us/library/system.windows.media.visual.aspx does not have any Properties for Height and Width.
Now, assuming you have a FrameworkElement, which does have a Height and Width property, are you using the Height and Width Properties, or the ActualHeight and ActualWidth properties?
If you are not, then those are the properties you are looking for.
If you are using them, or you are not using a FrameworkElement, then you should post some code.
If you could use animation to perform the Transform then animation provides Completed event. That should be a good place to extract new transformed size.
Based on #bor's answer, I simplified the code to allow a more common use:
private static Rect GetTransformedVisualBounds(Visual source, Visual target, Rect bounds)
{
return source.TransformToVisual(target).TransformBounds(bounds);
}
source is a Visual which can have transformations applied;
target is the Visual to which the coordinates will be converted to;
bounds is the non-transformed bounds of the source Visual;
The following is an example of use of the above method:
private void ExampleOfUse()
{
Border border = new Border()
{
Width = 100,
Height = 100
};
ContainerVisual container = new ContainerVisual();
container.Children.Add(border);
container.Transform = new ScaleTransform(2d, 2d);
Rect transformedBounds = GetTransformedVisualBounds(container, this, VisualTreeHelper.GetDescendantBounds(container));
// This should print a rectangle with a size of 200x200;
Debug.Print($"Transformed bounds: {transformedBounds}");
}
This is a very old question, but it was useful for me and I hope it is still useful for someone else as well.
public static Rect GetRelativePlacement(this UIElement element, UIElement relativeTo)
{
var absolutePos = element.PointToScreen(new Point(0, 0));
var posRelativeTo = relativeTo.PointToScreen(new Point(0, 0));
var topLeft = new Point(absolutePos.X - posRelativeTo.X, absolutePos.Y - posRelativeTo.Y);
var bottomRight = element.PointToScreen(new Point(element.RenderSize.Width, element.RenderSize.Height));
var bounds = Rect.Empty;
bounds.Union(topLeft);
bounds.Union(bottomRight);
return bounds;
}
GDI+ DrawLines function has a clipping bug that can be reproduced by running the following c# code. When running the code, two line paths appear, that should be identical, because both of them are inside the clipping region. But when the clipping region is set, one of the line segment is not drawn.
protected override void OnPaint(PaintEventArgs e)
{
PointF[] points = new PointF[] { new PointF(73.36f, 196),
new PointF(75.44f, 32),
new PointF(77.52f, 32),
new PointF(79.6f, 196),
new PointF(85.84f, 196) };
Rectangle b = new Rectangle(70, 32, 20, 164);
e.Graphics.SetClip(b);
e.Graphics.DrawLines(Pens.Red, points); // clipped incorrectly
e.Graphics.TranslateTransform(80, 0);
e.Graphics.ResetClip();
e.Graphics.DrawLines(Pens.Red, points);
}
Setting the antials mode on the graphics object resolves this. But that is not a real solution.
Does anybody know of a workaround?
It appears that this is a known bug...
The following code appears to function as you requested:
protected override void OnPaint(PaintEventArgs e)
{
PointF[] points = new PointF[] { new PointF(73.36f, 196),
new PointF(75.44f, 32),
new PointF(77.52f, 32),
new PointF(79.6f, 196),
new PointF(85.84f, 196) };
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
Rectangle b = new Rectangle(70, 32, 20, 165);
e.Graphics.SetClip(b);
e.Graphics.DrawLines(Pens.Red, points); // clipped incorrectly
e.Graphics.TranslateTransform(80, 0);
e.Graphics.ResetClip();
e.Graphics.DrawLines(Pens.Red, points);
}
Note: I have AntiAlias'ed the line and extended your clipping region by 1
it appears that the following work arounds might help (although not tested):
The pen is more than one pixel thick
The line is perfectly horizontal or vertical
The clipping is against the window boundaries rather than a clip rectangle
The following is a list of articles that might / or then again might not help:
http://www.tech-archive.net/pdf/Archive/Development/microsoft.public.win32.programmer.gdi/2004-08/0350.pdf
http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.gdi/2004-08/0368.html
OR...
the following is also possible:
protected override void OnPaint ( PaintEventArgs e )
{
PointF[] points = new PointF[] { new PointF(73.36f, 196),
new PointF(75.44f, 32),
new PointF(77.52f, 32),
new PointF(79.6f, 196),
new PointF(85.84f, 196) };
Rectangle b = new Rectangle( 70, 32, 20, 164 );
Region reg = new Region( b );
e.Graphics.SetClip( reg, System.Drawing.Drawing2D.CombineMode.Union);
e.Graphics.DrawLines( Pens.Red, points ); // clipped incorrectly
e.Graphics.TranslateTransform( 80, 0 );
e.Graphics.ResetClip();
e.Graphics.DrawLines( Pens.Red, points );
}
This effecivly clips using a region combined/unioned (I think) with the ClientRectangle of the canvas/Control. As the region is difned from the rectangle, the results should be what is expected. This code can be proven to work by adding
e.Graphics.FillRectangle( new SolidBrush( Color.Black ), b );
after the setClip() call. This clearly shows the black rectangle only appearing in the clipped region.
This could be a valid workaround if Anti-Aliasing the line is not an option.
Hope this helps
What appears to be the matter with the code?
OK, the question should be... what should the code do that it doesn't already.
When I run the code, I see 2 red 'spikes' am I not ment to?
You appear to draw the first spike within the clipped rectangle region verified by adding the the following after the declaration of teh Rectangle :
e.Graphics.FillRectangle( new SolidBrush( Color.Black ), b );
Then you perform a translation, reset the clip so at this point I assume the clientRectangle is being used as the appropriate clip region and then attempt to redarw the translated spike. Where's the bug?!?
The bug is that both line segments should be drawn identical but they are not because the spike that is drawn within the clipping region is completely within the clipping region and should not be clipped in any way but it is. This is a very annoying but that results in any software that uses drawlines heavily + clipping to look unprofessional because of gaps that can appear in the polygons.