I am trying to make a zoom effect on a picturebox with mouse wheel. Everything is OK except that when I use mouse middle button to zoom in or zoom out, it is ok, but it does not zoom in or zoom out the point which the mouse cursor is on. When I zoom in the point, I want it always slide. Please help me add a code snippet to make it work.
Here is my code:
int i = 5;
int index = 10;
private double[] zoomfactor = { .25, .33, .50, .66, .80, 1, 1.25, 1.5, 2.0, 2.5, 3.0 };
private void Zoom(int i)
{
double new_Zoom = zoomfactor[i];
imgBox.Width = Convert.ToInt32(imgBox.Image.Width * new_Zoom);
imgBox.Height = Convert.ToInt32(imgBox.Image.Height * new_Zoom);
}
private void On_wheel(object sender, System.Windows.Forms.MouseEventArgs e)
{
i = i + e.Delta / 120;
if (i < 0)
{
i = 0;
}
else
{
if (i <= index)
i = i;
else
i = index;
}
Zoom(i);
}
You need to adjust the picture box location based on the mouse position relative to the form.
Here is a rough but working example of how you might do this:
var i = 5;
var zoomfactor = new[] {.25, .33, .50, .66, .80, 1, 1.25, 1.5, 2.0, 2.5, 3.0};
var origin = new Point(100, 100);
var image = Image.FromFile(#"c:\example.png");
var imgBox = new PictureBox {
Location = origin,
Size = image.Size,
Image = image,
SizeMode = PictureBoxSizeMode.StretchImage
};
var form = new Form {
Size = new Size(800, 600),
Controls = {imgBox}
};
form.MouseWheel += (sender, e) => {
i += e.Delta/120;
if (i < 0) {
i = 0;
}
if (i >= zoomfactor.Length) {
i = zoomfactor.Length - 1;
}
var newZoom = zoomfactor[i];
imgBox.Width = (int) (imgBox.Image.Width*newZoom);
imgBox.Height = (int) (imgBox.Image.Height*newZoom);
imgBox.Left = (int) (e.X - newZoom*(e.X - origin.X));
imgBox.Top = (int) (e.Y - newZoom*(e.Y - origin.Y));
};
form.ShowDialog();
You are not taking the mouse coordinates into account.
The MouseEventArgs class tells you where the mouse is (X, Y and Location properties), so you need to adjust accordingly.
Related
I want to keep the window's position after the window has been resized. E.g, like how JPEGView handles resizing when changing image.
In WPF this code would accomplish it:
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
//Calculate half of the offset to move the window
if (sizeInfo.HeightChanged)
Top += (sizeInfo.PreviousSize.Height - sizeInfo.NewSize.Height) / 2;
if (sizeInfo.WidthChanged)
Left += (sizeInfo.PreviousSize.Width - sizeInfo.NewSize.Width) / 2;
}
How would you accomplish the same in AvaloniaUI?
Update:
I've tried subscribing to ClientSizeProperty.Changed, this just forces the window to be centered on the screen
ClientSizeProperty.Changed.Subscribe(
x =>
{
var newSize = new Size(
ClientSize.Width + (x.OldValue.Value.Width - x.NewValue.Value.Width) / 2,
ClientSize.Height + (x.OldValue.Value.Height - x.NewValue.Value.Height) / 2);
var rect = new PixelRect(
PixelPoint.Origin,
PixelSize.FromSize(newSize, scaling));
var screen = Screens.ScreenFromPoint(owner?.Position ?? Position);
if (screen != null)
{
Position = screen.WorkingArea.CenterRect(rect).Position;
}
});
I figured it out.
ClientSizeProperty.Changed.Subscribe(size =>
{
var x = (size.OldValue.Value.Width - size.NewValue.Value.Width) / 2;
var y = (size.OldValue.Value.Height - size.NewValue.Value.Height) / 2;
Position = new PixelPoint(Position.X + (int)x, Position.Y + (int)y);
});
I have to have static space (lets say 20 px) between axes.
If I have more axes on the left, it is possible to set their StartPosition and EndPosition in pixels or in percents.
For pixels setting - is there some way, how I can get the height of the space, where are the axes (red line in the picture)?
For percents setting - is there some way, how I can automatically convert 20 px to percents? I could calculate that by myself if I know the height, but I don't know how to get it - see 1.
I am able to get the height of the whole panel, where the chart is, but I don't know where to get the actual height of the space for left axes.
tChart1.Chart.ChartRect gives you the Rectangle of the "drawing zone". In your case, where you have to get that size before the axes calculations, you can use GetAxesChartRect event and use e.AxesChartRect as follows:
private void testChartRect()
{
for (int i = 0; i < 4; i++)
{
Line line = new Line(tChart1.Chart);
tChart1.Series.Add(line);
line.Chart = tChart1.Chart;
line.FillSampleValues();
Axis axis = new Axis();
tChart1.Axes.Custom.Add(axis);
line.CustomVertAxis = axis;
axis.AxisPen.Color = line.Color;
axis.Labels.Font.Color = line.Color;
}
tChart1.Aspect.View3D = false;
tChart1.Panel.MarginLeft = 10;
//tChart1.AfterDraw += TChart1_AfterDraw1;
tChart1.GetAxesChartRect += TChart1_GetAxesChartRect;
}
private void TChart1_GetAxesChartRect(object sender, GetAxesChartRectEventArgs e)
{
Rectangle chartRect = e.AxesChartRect;
int axisLength = (chartRect.Bottom - chartRect.Top) / tChart1.Axes.Custom.Count;
int margin = 20;
for (int i = 0; i < tChart1.Axes.Custom.Count; i++)
{
Axis axis = tChart1.Axes.Custom[i];
axis.StartEndPositionUnits = PositionUnits.Pixels;
axis.StartPosition = i * axisLength;
axis.EndPosition = (i + 1) * axisLength - (i != (tChart1.Axes.Custom.Count - 1) ? margin : 0);
}
}
private void TChart1_AfterDraw1(object sender, Graphics3D g)
{
tChart1.Graphics3D.Brush.Color = Color.Red;
tChart1.Graphics3D.Brush.Transparency = 80;
tChart1.Graphics3D.Rectangle(tChart1.Chart.ChartRect);
}
How can I enable zoom on a contentpage in xamarin forms? Is it possible to enable it on the entire contentpage? Or is it only possible to zoom in on images?
You can use the pinch gesture inside a ContentPage, here is the official page:
https://developer.xamarin.com/guides/xamarin-forms/user-interface/gestures/pinch/
and for the entire project sample:
https://github.com/xamarin/xamarin-forms-samples/tree/master/WorkingWithGestures/PinchGesture
Here is an example of what you can achieve:
Xamarin.Forms Pinch Example
Try this Class , this solution do not scroll when you zoom .
Source found here:
Source Code
public class PinchToZoomContainer : ContentView {
double currentScale = 1;
double startScale = 1;
double xOffset = 0;
double yOffset = 0;
public PinchToZoomContainer ()
{
var pinchGesture = new PinchGestureRecognizer ();
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add (pinchGesture);
}
void OnPinchUpdated (object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started) {
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running) {
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max (1, currentScale);
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the X pixel coordinate.
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the Y pixel coordinate.
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
// Calculate the transformed element pixel coordinates.
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
// Apply translation based on the change in origin.
Content.TranslationX = targetX.Clamp (-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp (-Content.Height * (currentScale - 1), 0);
// Apply scale factor
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed) {
// Store the translation delta's of the wrapped user interface element.
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
}
Helper DoubleExtensions
public static class DoubleExtensions
{
public static double Clamp (this double self, double min, double max)
{
return Math.Min (max, Math.Max (self, min));
}
}
You can try using the Scale Api on the Content page. This seemed to work for me on a small test app.
public App ()
{
// The root page of your application
var scaleUp = new Button {
Text = "Scale Up"
};
scaleUp.Clicked += (sender, e) => {
this.MainPage.Scale += 1;
};
var scaleDown = new Button {
Text = "Scale Down"
};
scaleDown.Clicked += (object sender, EventArgs e) => {
this.MainPage.Scale -= 1;
};
MainPage = new ContentPage {
Content = new StackLayout {
VerticalOptions = LayoutOptions.Center,
Children = {
scaleUp,
scaleDown
}
}
};
}
Trying to use this in conjunction with other controls that take the Pan/scroll gesture seems to fail. ScrollView and ListView for example grab the Pinch gesture even though they themselves don't Pinch but they do scroll/pan. So it seems like Pinch inherits/uses that gesture so it is grabbed by the wrapped control. This seems to make Pinch a very niche feature that can only work on very static views of Label and Image for example, but nothing more complex.
You can use gestures.
Sample here : http://arteksoftware.com/gesture-recognizers-with-xamarin-forms/
I don't understand something. If i don't use the customlabels, the chart will use the default label. And then if I move the scrollbar , the chart size won't adjust. The Chart view maintain the original size.
But if I use this code to change the label at row 0. (other rows don't have this problem)
chart1.ChartAreas[0].AxisY2.CustomLabels.Add((i) ,
(i+1), (ntemp * 10).ToString(), 0, LabelMarkStyle.SideMark);
And Move the scrollbar, the chart View will be a little different for size. The chart will flicker, and I don't want it.
Thanks in advance.
Here is example
Random rand = new Random();
chart1.Series.Clear();
var series = chart1.Series.Add("My Series");
series.ChartType = SeriesChartType.RangeBar;
series.Color = Color.Black;
series.YAxisType = AxisType.Secondary;
for (int i = 10; i > 2; i--)
series.Points.AddXY(i, (rand.Next(3600, 7200)), (rand.Next(30000, 80000)));
var chartArea = chart1.ChartAreas[series.ChartArea];
chartArea.BorderDashStyle = ChartDashStyle.Solid; //最外圍的框框
chartArea.BorderWidth = 10;
chartArea.AxisY.Enabled = AxisEnabled.False;
chartArea.AxisY2.Enabled = AxisEnabled.True;
chartArea.AxisY2.LabelStyle.IntervalType = DateTimeIntervalType.Number;
chartArea.AxisY2.Interval = 3600;
chartArea.AxisY2.Minimum = 0;
chartArea.AxisY2.Maximum = 86400;
chartArea.AxisY2.ScaleView.Zoom(0, 3600 * 4);
for (int i = 0; i <= 24 * 6; i++)
{
int ntemp = i % 6;
if (ntemp != 0)
{
/*Problem Here !!*/
//chart1.ChartAreas[0].AxisY2.CustomLabels.Add((i) * 600, (i + 1) * 600, (ntemp * 10).ToString(), 0, LabelMarkStyle.Box);
}
}
chartArea.CursorY.AutoScroll = true;
chartArea.AxisY2.ScaleView.Zoomable = true;
chartArea.AxisY2.ScrollBar.ButtonStyle = ScrollBarButtonStyles.SmallScroll;
chartArea.AxisY2.ScrollBar.IsPositionedInside = false;
}
Well, I was intrigued about how and if this can be achieved with OxyPlot, and I think it can ...
Here's the code I've used, and here's a screenshot:
var model = new PlotModel("IntervalBarSeries") { LegendPlacement = LegendPlacement.Outside };
var temp_serie = new IntervalBarSeries
{
Title = "IntervalBarSeries 1",
FillColor = OxyColors.Black
};
var categoryAxis = new CategoryAxis
{
Position = AxisPosition.Left,
IsZoomEnabled = false, // No zoom on this axis
IsPanEnabled = false, // Right mouse move won't affect this axis
MajorGridlineStyle = LineStyle.Solid
,StartPosition = 1, EndPosition = 0 // This will reverse the order
};
var valueAxis = new LinearAxis(AxisPosition.Top)
{
MinimumPadding = 0.1, MaximumPadding = 0.1,
IsZoomEnabled = true,
MajorGridlineStyle = LineStyle.Solid,
MajorStep = 3600,
AbsoluteMinimum = 0
};
for (int i = 10; i > 2; i--)
{
temp_serie.Items.Add(new IntervalBarItem {
Start = rand.Next(3600, 7200),
End = rand.Next(30000, 80000)
});
categoryAxis.Labels.Add("Activity "+i);
}
model.Series.Add(temp_serie);
model.Axes.Add(categoryAxis);
model.Axes.Add(valueAxis);
MyPlotModel = model;
Now, I'm using MVVM and just binding to the plot model from my View with:
<oxy:Plot Model="{Binding MyPlotModel}"/>
But you can figure out how to do the same with WinForms once (if?) you decide to use OxyPlot and import it.
I'm assuming you're doing some work that is related to times, but your code obviously doesn't say so ... you could play around with the top header, and maybe set how to show the numbers (ATM, with no zoom, they overlap each other a bit. zooming with scroller solves that, but that's just because i've set the tick size to 3600 ... )
this is the code I use to do the hitTest:
RayHitTestResult hit = VisualTreeHelper.HitTest(
App.MainWin.Viewport, location) as RayHitTestResult;
This code works when the camera width is small (i.e. objects are big). When I zoom out the camera (i.e. objects become smaller) to certain point, then when I click on the viewport, it shows an error on the above code, and the error details is like this:
System.NotSupportedException was unhandled : Hit testing with a
singular MatrixCamera is not supported.
I googled and found nobody having this error. Any idea how to solve it? Thanks!
The code I used to create / update the MatrixCamera: (They are inspired by the examples in the book "3D Programming for Windows: Three-Dimensional Graphics Programming for the Windows Presentation Foundation" by Charles Petzold, Chapter 7)
//create a new camera, initialize it and attach it to the viewport.
public void initCamera(Point3D position, Point3D AimPoint,
Vector3D upDirection, double farDistance,
double nearDistance, double Width)
{
this._Camera = new MatrixCamera();
this.CameraAimPoint = (Vector3D)AimPoint;
//check and adjust the camera depth if needed
this.CameraZAxis = Point3D.Subtract(position, AimPoint);
this.CameraDepth = CameraZAxis.Length;
this.CameraZAxis.Normalize();
this.CameraPosition = this.CameraAimPoint + (this.CameraZAxis * this.CameraDepth);
this.CameraFarDistance = farDistance;
this.CameraNearDistance = nearDistance;
this.CameraWidth = Width;
this.CameraXAxis = Vector3D.CrossProduct(upDirection, this.CameraZAxis);
this.CameraXAxis.Normalize();
this.CameraYAxis = Vector3D.CrossProduct(this.CameraZAxis, this.CameraXAxis);
this._CameraViewMatrix = new Matrix3D();
this._CameraViewMatrix.M14 = 0;
this._CameraViewMatrix.M24 = 0;
this._CameraViewMatrix.M34 = 0;
this._CameraViewMatrix.M44 = 1;
this.updateViewMatrix();
this._CameraProjectMatrix = new Matrix3D();
this._CameraProjectMatrix.M14 = 0;
this._CameraProjectMatrix.M24 = 0;
this._CameraProjectMatrix.M34 = 0;
this._CameraProjectMatrix.M44 = 1;
this.updateProjectionMatrix();
this._Viewport.Camera = this._Camera;
}
private void updateViewMatrix(bool axisChanged=true)
{
if (axisChanged==true)
{
this._CameraViewMatrix.M11 = this.CameraXAxis.X;
this._CameraViewMatrix.M12 = this.CameraYAxis.X;
this._CameraViewMatrix.M13 = this.CameraZAxis.X;
this._CameraViewMatrix.M21 = this.CameraXAxis.Y;
this._CameraViewMatrix.M22 = this.CameraYAxis.Y;
this._CameraViewMatrix.M23 = this.CameraZAxis.Y;
this._CameraViewMatrix.M31 = this.CameraXAxis.Z;
this._CameraViewMatrix.M32 = this.CameraYAxis.Z;
this._CameraViewMatrix.M33 = this.CameraZAxis.Z;
}
this._CameraViewMatrix.OffsetX = -Vector3D.DotProduct(this.CameraXAxis, this.CameraPosition);
this._CameraViewMatrix.OffsetY = -Vector3D.DotProduct(this.CameraYAxis, this.CameraPosition);
this._CameraViewMatrix.OffsetZ = -Vector3D.DotProduct(this.CameraZAxis, this.CameraPosition);
this._Camera.ViewMatrix = this._CameraViewMatrix;
this._3DTo2DTransformMatrixIsDefined = false;
AfterDraw();
}
//
private void updateProjectionMatrix() {
double ScaleX = 2 / CameraWidth;
double ScaleY = _Viewport.ActualWidth / _Viewport.ActualHeight * ScaleX;
double ScaleZ = 1 / (CameraNearDistance - CameraFarDistance);
double zOffset = CameraNearDistance * ScaleZ;
_CameraProjectMatrix.M11 = ScaleX;
_CameraProjectMatrix.M22 = ScaleY;
_CameraProjectMatrix.M33 = ScaleZ;
_CameraProjectMatrix.OffsetZ = zOffset;
_Camera.ProjectionMatrix = _CameraProjectMatrix;
_3DTo2DTransformMatrixIsDefined = false;
_PixelToWorldUnit = CameraWidth / _Viewport.ActualWidth;
AfterDraw();
}
The code to perform camera zooming:
private void _cameraZoomToScale(double width)
{
if (width< 1) width = 1;
this.CameraWidth = width;
updateProjectionMatrix();
}
It seems pretty clear that you are zooming further than the MatrixCamera supports. Figure out at what zoom level this exception occurs and prevent the user from getting that far. It looks like you might already be trying to do that already with if (scale < 1) scale = 1; but scale doesn't seem to be used elsewhere in the posted code.