I have a WPF Application where I need to draw a large slider. The problem is drawing that large slider has a severe performance issue. What I did was reduce the size of the slider and redraw it when is needed. That is when Scrollviewer is scrolled and the slider is out of range, I place the slider in the Correct position and set it's min and max value.
It works fine until I want to reach the end near my thumb, it is rendered in a different place and interacts from a different one, given that there is very large area to scroll.
As you can see in the picture I need to place mouse in the red zone to drag the thumb but the thumb is drawn in a different position.
If anyone of you have a solution to this problem and know the reason, it would be a great help.
Sample Code is given below
Thanks in Advance.
Move the Scroll to the end and try dragging the thumb to reproduce the
problem.
MainWindow.xaml
<Window x:Class="SLiderTestApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350"
Loaded="MainWindow_OnLoaded">
<ScrollViewer x:Name="scrollViewer"
HorizontalScrollBarVisibility="Visible"
ScrollChanged="ScrollViewer_OnScrollChanged">
<Canvas x:Name="canvas" Width="600000000">
<Grid x:Name="_grid" Width="10000">
<Slider x:Name="slider" />
</Grid>
</Canvas>
</ScrollViewer>
</Window>
MainWindow.cs
namespace SLiderTestApplication
{
#region
using System.Windows;
using System.Windows.Controls;
#endregion
public partial class MainWindow : Window
{
#region Constructors and Destructors
public MainWindow()
{
this.InitializeComponent();
}
#endregion
#region Methods
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
this.slider.Maximum = 10000;
this.slider.Value = this.slider.Maximum;
}
private void ScrollViewer_OnScrollChanged(object sender, ScrollChangedEventArgs e)
{
var left = this.scrollViewer.HorizontalOffset - this.slider.ActualWidth + 200;
if (left < 0)
{
left = 0;
}
this._grid.Margin = new Thickness(left, 0, 0, 0);
}
#endregion
}
}
Related
I'm trying to draw Microsoft.UI.Xaml.Shapes.Path objects to a Microsoft.UI.Xaml.Controls.Canvas control. These objects are configured in their own coordinate system, and transformed to the pixel-space coordinate system using the Transform property of the Geometry objects.
However, I'm getting some odd behaviour that I'm struggling to make sense of. Here is code that replicates the issue:
XAML
<Window
x:Class="TransformRenderBug.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TransformRenderBug"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
SizeChanged="Window_SizeChanged">
<TabView >
<TabViewItem Header="Blank tab"/>
<TabViewItem Header="Drawing tab">
<local:NumberSpace x:Name="NumberSpace" >
<Path Stroke="Red" StrokeThickness="1">
<Path.Data>
<EllipseGeometry RadiusX="1" RadiusY="1" Transform="{x:Bind Transform}"/>
</Path.Data>
</Path>
<Path Stroke="Green" StrokeThickness="1">
<Path.Data>
<EllipseGeometry RadiusX="0.9" RadiusY="0.9" Transform="{x:Bind NumberSpace.Transform}"/>
</Path.Data>
</Path>
<local:NumberSpacePath Stroke="Blue" StrokeThickness="1">
<local:NumberSpacePath.Data>
<EllipseGeometry RadiusX="0.8" RadiusY="0.8" Transform="{x:Bind Transform}"/>
</local:NumberSpacePath.Data>
</local:NumberSpacePath>
<local:NumberSpacePath Stroke="Black" StrokeThickness="1">
<local:NumberSpacePath.Data>
<EllipseGeometry RadiusX="0.7" RadiusY="0.7" Transform="{x:Bind NumberSpace.Transform}"/>
</local:NumberSpacePath.Data>
</local:NumberSpacePath>
</local:NumberSpace>
</TabViewItem>
</TabView>
</Window>
C#
public sealed partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private CompositeTransform Transform { get; } = new();
private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs args)
{
NumberSpace.SetTransform(Transform, args.Size.Width, args.Size.Height - 40);
NumberSpace.Width = args.Size.Width;
NumberSpace.Height = args.Size.Height - 40;
}
}
public class NumberSpace : Canvas
{
public CompositeTransform Transform { get; } = new();
public NumberSpace()
{
SizeChanged += (o, e) => SetTransform(Transform, ActualWidth, ActualHeight);
}
public static void SetTransform(CompositeTransform transform, double width, double height)
{
// Figure out the transform required for the shapes to fill the space
double pixelScaleX = width / 2;
double pixelScaleY = height / 2;
transform.ScaleX = pixelScaleX;
transform.ScaleY = pixelScaleY;
transform.TranslateX = pixelScaleX;
transform.TranslateY = pixelScaleY;
}
}
public class NumberSpacePath : Path
{
}
This code produces a window with two tabs. The first is blank. The second has four concentric ellipses drawn on it which take their transforms from either the Window or the Canvas. The ellipses are drawn as either Path objects or NumberSpacePath objects. NumberSpacePath inherits from Path but adds and changes nothing.
The two outer rings draw and resize without issue. However, when the second tab is first switched to, the innermost black ring is not rendered. Switching back to the first tab and again to the second tab causes it to be rendered. Additionally, enlarging the window causes the innermost two rings to be clipped, like on the image below. Again, switching and returning tabs causes them to be rendered correctly.
What is going on? Why does subclassing Path make a difference?
I tried to create a screenshot function, set the "Canvas" background as a picture, press and raise the mouse to draw a rectangle, and capture the content inside the rectangle, but my mouse up event is always invalid. I searched all kinds of information and tried, all of them seem to be invalid
public Bitmap GetScreenSnapshot()
{
try
{
System.Drawing.Rectangle rc = SystemInformation.VirtualScreen;
var bitmap = new Bitmap(rc.Width, rc.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (Graphics memoryGrahics = Graphics.FromImage(bitmap))
{
memoryGrahics.CopyFromScreen(rc.X, rc.Y, 0, 0, rc.Size, CopyPixelOperation.SourceCopy);
}
return bitmap;
}
catch (Exception)
{
}
return null;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var screenSnapshot = GetScreenSnapshot();
var bmp = ImageProcessing.ToBitmapSource(screenSnapshot);
bmp.Freeze();
Clipper clipper = new Clipper();
clipper.bitmap = screenSnapshot;//将图片传过去
clipper.Background = new ImageBrush(bmp);
clipper.Show();
}
Here, I use a button to create a Window (Clipper) in the main window, and provide the obtained desktop image to Canvas
<Window x:Class="MyOCR_WPF.Clipper"
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:MyOCR_WPF"
mc:Ignorable="d"
Title="Clipper" Height="450" Width="800" WindowStyle="None" WindowState="Maximized" WindowStartupLocation="CenterScreen">
<Canvas x:Name="canvas" MouseMove="canvas_MouseMove" MouseDown="canvas_MouseDown" MouseUp="canvas_MouseUp" Focusable="True" Background="Transparent">
</Canvas>
Its mouse up event is always invalid
Mouseup is invalid because you have drawn a Rectangle on cancas.When mouseup is triggered on Rectangle, the mouseup of Cancas cannot be triggered.
I tested and found no PreviewMouseUp on Cancas either.
I used a different solution to your problem.
view
<Grid>
<Canvas x:Name="canvas" Focusable="False" />
<Border MouseMove="canvas_MouseMove" MouseDown="canvas_MouseDown" MouseUp="canvas_MouseUp" Background="Transparent"/>
</Grid>
I am creating shapes at run time on canvas in my app and except all the shapes, ellipse is going out of canvas. How do I restrict it to canvas? All other shapes are contained in canvas because of the control points at their vertices. How do I keep a check as to not let ellipse go out of canvas without clipping. I have used ClipToBounds and it doesn't meet my needs.
Also, an alternate solution is if I can add a controlpoint at the left side of ellipse of radiusX property. I can't add a controlpoint to left side of radiusX on ellipse. If you could help me with either of that?
radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusYProperty, 1, true, false);
radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
shape_ControlPoints.Add(radiusXcp);
radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusXProperty, 0, true, false);
radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
shape_ControlPoints.Add(radiusXcp);
//EllipseGeom.RadiusX = -EllipseGeom.RadiusX;
//radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusXProperty, 0, true, false);
//radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
//shape_ControlPoints.Add(radiusXcp);
//EllipseGeom.RadiusX = -EllipseGeom.RadiusX;
Here is a quick example of what i would do. It could be improved on and the code is mainly written to be easy to read and follow. It also does not handle the possibility to if the shape's size is bigger than the Canvas (not sure if that is a use case in your project).
For the example I used the "Loaded" event on the Canvas, to reset the position before drawing. You would want this check before you draw the Ellipse object.
private void TestCanvas_Loaded(object sender, RoutedEventArgs e)
{
//canvas = 450 x 800
Ellipse test_ellipse = new Ellipse();
test_ellipse.Width = 100;
test_ellipse.Height = 100;
test_ellipse.Fill = Brushes.Red;
Canvas.SetLeft(test_ellipse, 700);
Canvas.SetTop(test_ellipse, -500);
Reset_Ellipse_Bounds(TestCanvas, ref test_ellipse);
TestCanvas.Children.Add(test_ellipse);
}
private void Reset_Ellipse_Bounds(Canvas myCanvas, ref Ellipse myEllipse)
{
var left = Canvas.GetLeft(myEllipse);
var top = Canvas.GetTop(myEllipse);
//handle too far right
if (left + myEllipse.Width > myCanvas.ActualWidth)
Canvas.SetLeft(myEllipse, myCanvas.ActualWidth - myEllipse.Width);
//handle too far left
if(left < 0)
Canvas.SetLeft(myEllipse, 0);
//handle too far up
if (top < 0)
Canvas.SetTop(myEllipse, 0);
//handle too far down
if (top + myEllipse.Height > myCanvas.ActualHeight)
Canvas.SetTop(myEllipse, myCanvas.ActualHeight - myEllipse.Height);
}
For Completeness the XAML:
<Grid>
<Canvas x:Name="TestCanvas" Loaded="TestCanvas_Loaded" />
</Grid>
The idea is to check the bounding box against the Canvas edges. There are ways to improve this, but i figured the simplest solution is easier to follow.
Within each if statement you could add more logic or a method to do further processing, but this should answer the general question of knowing if it is outside the parent.
Just set ClipToBounds="true" to its father control, it avoids the canvas to be drawn outside of it.
In my case I set it to Grid as followed :
<Grid x:Name="MainGrid" Background="WhiteSmoke" ClipToBounds="true">
<Canvas Margin="10" Background="Transparent"
SizeChanged="ViewportSizeChanged"
MouseLeftButtonDown="ViewportMouseLeftButtonDown"
MouseLeftButtonUp="ViewportMouseLeftButtonUp"
MouseMove="ViewportMouseMove"
MouseWheel="ViewportMouseWheel">
<Canvas x:Name="canvas" Width="1000" Height="600"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.RenderTransform>
<MatrixTransform x:Name="transform"/>
</Canvas.RenderTransform>
</Canvas>
<Canvas x:Name="canvas2" Width="1000" Height="600"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.RenderTransform>
<MatrixTransform x:Name="transform2"/>
</Canvas.RenderTransform>
</Canvas>
</Canvas>
</Grid>
I am working on a WPF project, trying to create an analog clock. I have an image of a clock (without the hands) and have set it as a background. I also have images of two hands of clock, which I want to rotate keeping one end fixed (like it happens in a clock). How can I rotate the image keeping its one end fixed in C#.NET (WPF)? What I have tried is the following code:
namespace AnalogWatch
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
DispatcherTimer dispatcherTimer;
private int degrees = 0;
public MainWindow()
{
InitializeComponent();
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
degrees += 5;
if (degrees > 360)
{
degrees = 0;
}
RotateTransform transform = new RotateTransform(degrees,StickImg.Width/2,StickImg.Height/2);
//StickImg.RenderTransformOrigin = new System.Windows.Point(0, 0);
StickImg.RenderTransform = transform;
}
private void Window_ContentRendered_1(object sender, EventArgs e)
{
dispatcherTimer.Start();
}
}
}
It is rotating the image but not keeping its one end fixed. What is the problem here ?
XAML is:
<Window x:Class="AnalogWatch.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" ContentRendered="Window_ContentRendered_1">
<Grid>
<Image x:Name="StickImg" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" Margin="207,70,0,0" Source="stick.png"/>
</Grid>
</Window>
Updated.
Your code should work if clock hand exactly in the center of rectangular image.
Like this one
You can do either like you did
RotateTransform transform =new RotateTransform(degrees, StickImg.Width/2,StickImg.Height/2);
or
RotateTransform transform = new RotateTransform(degrees);
StickImg.RenderTransformOrigin = new Point(0.5, 0.5);
Use the RenderTransformOrigin of the element to set the center of rotation.
Note that the coordinates are scaled to 0..1
So to rotate around the center:
RenderTransformOrigin="0.5, 0.5"
Just make sure that the pivot of the hand is in the center of the element.
here's the code i'm using and it works like charm..as qwr suggested clock hand should be exactly in the center of rectangular image
c# code
DispatcherTimer clock = new DispatcherTimer();
public AnalogClock()
{
InitializeComponent();
clock.Interval =TimeSpan.FromMilliseconds(100);
clock.Tick += clock_Tick;
clock.Start();
}
void clock_Tick(object sender, EventArgs e)
{
double milsec = DateTime.Now.Millisecond;
double sec = DateTime.Now.Second;
double min = DateTime.Now.Minute;
double hr = DateTime.Now.Hour;
seconds.LayoutTransform = new RotateTransform(((sec / 60) * 360)+((milsec/1000)*6));
minutes.LayoutTransform = new RotateTransform((min * 360 / 60)+((sec/60)*6));
hours.LayoutTransform = new RotateTransform((hr * 360 / 12)+(min/2));
}
and the XAML code for images
<Grid Margin="-100">
<Image x:Name="clockface" RenderOptions.BitmapScalingMode="HighQuality" Source="images/panel.PNG" RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Center" VerticalAlignment="Center" Height="194" Margin="100" Width="194"/>
<Image x:Name="hours" RenderOptions.BitmapScalingMode="HighQuality" Source="images/hours.PNG" RenderTransformOrigin="0.5,0.5" VerticalAlignment="Center" HorizontalAlignment="Center" Height="194" Margin="100" Width="194"/>
<Image x:Name="minutes" RenderOptions.BitmapScalingMode="HighQuality" Source="images/minutes.PNG" RenderTransformOrigin="0.5,0.5" VerticalAlignment="Center" HorizontalAlignment="Center" Height="194" Margin="100" Width="194"/>
<Image x:Name="seconds" RenderOptions.BitmapScalingMode="HighQuality" Source="images/seconds.PNG" RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Center" VerticalAlignment="Center" Height="194" Margin="100" Width="194"/>
</Grid>
note the i've used the code in a separate usercontrol..
just make sure that the margin between the clock hand image and the grid i'ts contained in should be enough to make room for the image to rotate else it will displace while rotating..
hope this helps..!
I have an image (inside canvas). I need to specify the coordinates of the image in text box, and I want the image center to be in this point (not its top-left corner). How can I do that?
Update: I don't need to place my image in the center of the canvas. I want the position of the image to be defined by image center. Example, I have a code: <Image source"..." canvas.Left="0" canvas.Top="0">, and this point (0,0) means that the image center is in the top-left corner of the canvas.
Set the position using Attached properties:
Canvas.Left
Canvas.Top
If you want to place at center, set
Canvas.SetLeft(image, (canva.Width - Image.Width) / 2);
Canvas.SetTop(image, (canva.Height- Image.Height) / 2);
Also look Align images in WPF canvas in center
Update
Canvas.SetLeft(image, Image.Width / 2);
Canvas.SetTop(image, Image.Height / 2);
You can set it from behaviors for example
<Window x:Class="WpfSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:wpfSample="clr-namespace:WpfSample"
Title="MainWindow"
Width="800"
Height="800"
Background="Gray">
<Border Width="400"
Height="400"
HorizontalAlignment="Center"
VerticalAlignment="Center"
BorderBrush="Black"
BorderThickness="2">
<Canvas >
<Rectangle Fill="OrangeRed" Width="150" Height="150">
<i:Interaction.Behaviors>
<wpfSample:CenterBehavior/>
</i:Interaction.Behaviors>
</Rectangle>
</Canvas>
</Border>
</Window>
and the behavior (raw sample)
public class CenterBehavior : Behavior<FrameworkElement>
{
protected override void OnAttached()
{
base.OnAttached();
UpdatePosition();
AssociatedObject.SizeChanged += OnSizeChanged;
}
private void UpdatePosition()
{
Canvas.SetLeft(AssociatedObject, -AssociatedObject.Width/2);
Canvas.SetTop(AssociatedObject, -AssociatedObject.Height/2);
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
UpdatePosition();
}
}
Just subtract half of your Image.Width value your current Canvas.Left value and subtract half of your Image.Height value your current Canvas.Top value.