I am currently building a Custom Scroll Bar (pictured above) I am able to set the scrollbar value to any number within the allowed range (between Min and Max), but when it comes to mouse scrolling I have a maths problem.
I want the mouse to remain at the same position where the user clicked in the thumb bar, and then drag the thumb up or down, so that the mid position of the scroll bar reflects the value.
I have the following code to rest the mid position of the thumb:
MoveThumb()
{
m_iMidPoint = Mouse.Cursor.Y;
int iHalfThumb = ThumbLength / 2;
m_iMidPoint += iHalfThumb;
// Clamp to allowable range
m_iMidPoint = m_iMidPoint < (TrenchStartPixel + iHalfThumb)
? TrenchStartPixel + iHalfThumb
: m_iMidPoint;
m_iMidPoint = m_iMidPoint > (TrenchEndPixel - iHalfThumb)
? TrenchEndPixel - iHalfThumb
: m_iMidPoint;
Value = MidThumbToValue(m_iMidPoint); // This property gets clamped
}
public int ConvertRange( int originalStart, int originalEnd, int newStart,
int newEnd, int value)
{
double scale = (double)(newEnd - newStart) / (originalEnd - originalStart);
return (int)(newStart + ((value - originalStart) * scale));
}
public int ValueToMidThumb(int iValue)
{
return ConvertRange(Minimum, Maximum, TrenchStartPixel + (ThumbLength/2),
TrenchEndPixel - (ThumbLength/2), iValue);
}
public int MidThumbToValue(int iValue)
{
return ConvertRange(TrenchStartPixel + (ThumbLength /2),
TrenchEndPixel - (ThumbLength /2), Minimum, Maximum, iValue);
}
This code works ok, APART from the fact that Thumb always jumps so that the middle of the thumb tracks the mouse.
How can I get the thumb to track the mouse position without the jumping?
The part I was missing, was the fact that a user presses the mouse button down to begin dragging the thumb. At that point the offset from the thumb middle position can be calculated and used while dragging the thumb. This offset will remain constant until the user releases the mouse. The fact that is remains constant throughout the sequence of events, it can be used during the mouse move.
pseudo-code:
MouseDownEvent(MouseEventArgs e) {
m_mouseOffset = e.Y - Thumb.Top + (ThumbLength / 2);
}
MouseMoveEvent(MouseEventArgs e) {
int iPos = e.Y;
iPos -= m_mouseOffset;
m_iMidPoint= iPos;
}
Related
I have an app where I am drawing lines through points to form polygons, I want to be able to have the four corners of the polygon be moveable, I have this code inside of a custom view I've created for drawing polygons:
public override bool OnTouchEvent(MotionEvent e)
{
MotionEventActions action = e.Action;
float x = 0.0f;
float y = 0.0f;
float radius = 20.0f;
// Get the current pointer index
int tempPointerIndex = e.ActionIndex;
x = e.GetX(tempPointerIndex);
y = e.GetY(tempPointerIndex);
// For each point, check to see if the touch fell within the bounds of the circle
for (int i = 0; i < _points.Length; i++)
{
if((x >= _points[i].X - radius && x <= _points[i].X + 20) ||
(y >= _points[i].Y - radius && y >= _points[i].Y + 20))
{
switch (action)
{
case MotionEventActions.Down:
{
// Get the current x/y when not moving so we can calculate the delta x/y in
// move events
lastX = x;
lastY = y;
// Set the global active pointer index
activePointerId = e.GetPointerId(0);
break;
}
case MotionEventActions.Move:
{
int pointerIndex = e.FindPointerIndex(activePointerId);
x = e.GetX(pointerIndex);
y = e.GetY(pointerIndex);
// Find the delta x/y
float deltaX = x - lastX;
float deltaY = y - lastY;
// Move the selected skew handle by the delta x/y
_points[i].X += deltaX;
_points[i].Y += deltaY;
// Force a redraw
this.Invalidate();
break;
}
case MotionEventActions.Up:
{
// Invalidate the active pointer index
activePointerId = global::Android.Views.MotionEvent.InvalidPointerId;
break;
}
case MotionEventActions.Cancel:
{
// Invalidate the active pointer index
activePointerId = global::Android.Views.MotionEvent.InvalidPointerId;
break;
}
case MotionEventActions.PointerUp:
{
int pointerIndex = e.ActionIndex;
int pointerId = e.GetPointerId(pointerIndex);
if(pointerId == activePointerId)
{
// Active pointer going up, choose new active pointer and adjust accordingly
int newPointerIndex = pointerIndex == 0 ? 1 : 0;
lastX = e.GetX(newPointerIndex);
lastY = e.GetY(newPointerIndex);
activePointerId = e.GetPointerId(newPointerIndex);
}
break;
}
}
}
}
return base.OnTouchEvent(e);
}
The case for MotionEventActions.Move never seems to get hit, in fact, the only event that seems to get hit is the down event when I initially hit a point. Also, the condition that checks the bounds of the point works for sure, I've tested it numerous times, so that isn't the issue either, I'm not sure if maybe I'm checking for the event incorrectly, but as far as I can tell this is how both the Xamarin and Android documentation say to handle this event, so I'm not really sure what the problem is, or how to find it. My question is, what exactly am I doing wrong that is causing the move event to not be picked up?
Once you determine if one of your points is in range of the user's touch, start returning true from OnTouchEvent until you get an Up, that way all future touch events become Move events on your custom View and are not redirected to the View's parent.
Down/Move/Up touch event logic:
public override bool OnTouchEvent(MotionEvent e)
{
x = e.GetX(e.ActionIndex);
y = e.GetY(e.ActionIndex);
Log.Debug(TAG, $"{e.Action} : {x} : {y}" );
if (e.Action == MotionEventActions.Down)
{
if (true) // some check if the x/y touch is on or near a moveable point, isDraggingPoint becomes true
isDraggingPoint = true;
else
isDraggingPoint = false;
}
if (e.Action == MotionEventActions.Move)
{
// move your Point, x/y touch for draggingPoint, update display, etc...
}
if (e.Action == MotionEventActions.Up)
{
// save the final x/y touch for draggingPoint, update display, etc...
isDraggingPoint = false;
}
return isDraggingPoint;
}
Output:
[CustomView] Down : 358.5332 : 234.6167
[CustomView] Move : 358.5332 : 234.6167
[CustomView] Move : 352.5305 : 238.6241
[CustomView] Move : 334.8269 : 237.3517
[CustomView] Move : 360.5305 : 246.6073
[CustomView] Move : 360.5305 : 246.6073
[CustomView] Up : 360.5305 : 246.6073
So I have a PictureBox and the user is supposed to drag the cursor inside it towards the direction shown by the arrow (in the PictureBox), but I'm not sure how I will set the coordinates to make sure the user dragged in the correct direction (up, down, right, or left).
private void picArrow_MouseDown(object sender, MouseEventArgs e)
{
mPointDown = new Point(e.X, e.Y);
//lblTest.Text = "X: " + mPointDown.X.ToString() + " Y: " + mPointDown.Y.ToString();
}
private void picArrow_MouseUp(object sender, MouseEventArgs e)
{
lblTest.Text += " " + " X: " + e.X.ToString() + " Y: " + e.Y.ToString();
//MessageBox.Show("Entered mouseup");
//rnd_Arr refers to the number of the arrow being shown, 0 = towards the right
if (rnd_Arr == 0 && mPointDown.X >= 0 && mPointDown.Y >= 0 && e.X >= 40 && e.Y >= 0)
{
//some code
}
else
{
MessageBox.Show("DONE!");
}
}
And I know this code doesn't work because even if the user drags down (when he's supposed to drag up), it still accepts it and increments the score.
I'm not putting too much restrictions. It doesn't have to be in a perfectly straight line, or start and end in exact locations. As long as the user is dragging inside the PictureBox and dragging to the correct direction, or at least reaches the minimum length to make sure it is considerable that the user is dragging to the correct direction, like for example:
The arrow shown is pointing to the right. The user doesn't have to drag all the way through the arrow, if he drags horizontally past 40px (and the whole length of the arrow is, say, 80px), then that'll add a point to his score. Nevertheless, I'm deliberating this part, if I should just be more demanding and require the user to drag all the way through.
Should I remove the mouse events for the PictureBox and add mouse events for the form instead?
Thank you!
You're going to want to check the MouseDown.X and compare it to the MouseUp.X (or Y if you want to check vertical direction as well). It is important to note that (0, 0) is the upper left of your screen.
Start by subtracting MouseUp.X from MouseDown.X to get a delta X. This is your change in X pixels over the move operation.
If your X comparison yields a negative delta (MouseUp.X is smaller than MouseDown.X), your mouse has moved left.
If your X comparison yields a positive delta (MouseUp.X is larger than MouseDown.X), your mouse has moved right.
The same concept applies to the Y coord, positive change means you moved the mouse down and a negative changemeans you moved the mouse up. See below code:
Point mouseDownPoint;
Point mouseUpPoint;
float deltaX = mouseUpPoint.X - mouseDownPoint.X;
float deltaY = mouseUpPoint.Y - mouseDownPoint.Y;
if (deltaX > 0)
{
// Moved right
}
else if (deltaX < 0)
{
// Moved left
}
if (deltaY > 0)
{
// Moved down
}
else if (deltaY < 0)
{
// Moved up
}
private void picArrow_MouseUp(object sender, MouseEventArgs e)
{
bool movedUp, movedDown, movedLeft, movedRight;
if (e.X == mPointDown.X) { movedRight = movedLeft = false; }
else { movedRight = e.X < mPointDown.X; movedLeft = !movedRight; }
if (e.Y == mPointDown.Y) { movedUp = movedDown = false; }
else { movedUp = e.Y < mPointDown.Y; movedDown = !movedUp; }
// Code can now use the Booleans above as needed
// . . .
}
i have taken one Flow layout panel and placed multiple picture box inside in it. now i want when i will place my mouse at the right or left most edge of the Flow layout panel then rest of picture will scroll out. just think about windows 8 start screen where many tiles appear in screen and when we place mouse at right most edge on the screen then rest of the tiles scroll out. i want to simulate same thing in windows form with Flow layout panel.
i want my Flow layout panel will not show scroll bar but images will scroll out when i will place mouse right or left most part on the panel. here is my screen shot
some one told me to do it this way...here is bit code
Set AutoScrollPosition property in MouseMove event of Panel.
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
panel1.AutoScrollPosition = new Point(e.X, e.Y);
}
but this trick was not good. AutoScrollPosition works when scroll bar is visible but in my case i do not want to show scroll bar with Flow layout panel. i want smooth scrolling images from left to right or right to left. anyone can help me to achieve what i am trying to do....if possible guide me with respect of coding. thanks
EDIT
Here i am giving my full code after modification following #Taw suggestion but it is not working fine....rather flickering found when picture move. anyway here is the full code.
namespace ScrollTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
flowLayoutPanel1.MouseMove += MouseScroll;
foreach (Control x in this.Controls)
{
if (x is PictureBox)
{
((PictureBox)x).MouseMove += MouseScroll;
}
}
}
int near = 33;
private void MouseScroll(object sender, MouseEventArgs e)
{
Point mouse = flowLayoutPanel1.PointToClient(MousePosition);
Rectangle C = flowLayoutPanel1.ClientRectangle;
int dLeft = mouse.X - C.Left;
int dTop = mouse.Y - C.Top;
int dRight = C.Right - mouse.X;
int dBottom = C.Bottom - mouse.Y;
int dX = dLeft < near ? dLeft : dRight < near ? -dRight : 0;
int dY = dTop < near ? dTop : dBottom < near ? -dBottom : 0;
if (dX != 0 | dY != 0) scrollFLP(dX, dY);
}
void scrollFLP(int deltaX, int deltaY)
{
flowLayoutPanel1.Left += getSpeedFromDistance(deltaX);
flowLayoutPanel1.Top += getSpeedFromDistance(deltaY);
System.Threading.Thread.Sleep(11);
}
int getSpeedFromDistance(int delta)
{
int sig = Math.Sign(delta);
int d = Math.Abs(delta);
if (d > near / 2) return sig;
else if (d > near / 3) return near / 10 * sig;
else if (d > near / 4) return near / 8 * sig;
else if (d > near / 5) return near / 5 * sig;
else return near * sig;
}
}
}
basically i am trying achieve something like suppose i have flow layout panel and which has many picture box inside it with many images as the screen shot but scroll bar should not show rather scroll will happen automatically when i will place my mouse at the top or bottom of the flow layout panel like carousel.
see this picture of your application
when place my mouse at the right end then it scroll and form background shown which i do not want. i want picture box will scroll & scroll upto last one not more than that.
any idea how to do it. thanks
2nd Edit
this code i added as per your suggestion
public Form1()
{
InitializeComponent();
for (int i = 0; i < 666; i++)
{
PictureBox pan = new PictureBox();
//pan.MouseMove += MouseScroll;
//pan.MouseLeave += outSideCheck;
pan.Size = new Size(75, 75);
pan.BackColor = Color.FromArgb(255, (i * 2) & 255, (i * 7) & 255, (i * 4) & 255);
flowLayoutPanel1.Controls.Add(pan);
}
//flowLayoutPanel1.MouseMove += MouseScroll;
//this.flowLayoutPanel1.MouseLeave += outSideCheck;
mouseScroller MSC = new mouseScroller();
MSC.registerControl(flowLayoutPanel1); // FLP = your FlowLayouPanel
MSC.timerSpeed = 5; // optional
MSC.nearness = 100; // optional
flowLayoutPanel1.AutoScroll = false;
}
now the apps doing wired behavior after adding new code. if i am making any mistake then guide me please. thanks
This is a two-part problem:
How to grab the event
How to scroll a FlowLayoutPanel with its scrollbars invisible.
Second first. It is not an easy task from what I found, unless you use a simple and rather common trick: Don't actually scroll it! Instead place it into a Panel and then control its position inside that Panel.
To do this you add a Panel panel1 to your Form, Dock or Anchor it as you need to and set its Autoscroll = false (!) (Which is not the way it is usually done, when you want to make, say a PictureBox scrollable. But we don't want the Panel to show it Scrollbars either.)
Set the FLP to its desired size and place it into the Panel, it obviously also has Autoscroll = false, and we're ready to tackle the other problem of setting up the event..:
First you add the MouseScroll event below to your code and then you hook every control up to it you want to work with the mouse move, namely the FLP:
flowLayoutPanel1.MouseMove += MouseScroll;
..and also each of your PictureBoxes, maybe like this
// your creation loop..
PictureBox pbox = new PictureBox();
pbox.MouseMove += MouseScroll; // <<--- hook into to the mousemove
pan.MouseLeave += outSideCheck; // <<--- hook into to the mouseleave
// .. do your stuff.. here I put some paint on to test..
pbox.BackColor = Color.FromArgb(255, 111, (i * 3) & 255, (i * 4) & 255);
flowLayoutPanel1.Controls.Add(pbox);
or however you create them..
Edit 2 I have changed my original code once more. It now includes an outside check, a check for moving towards the closest edge and a workaround for tha mousemove bug. It uses a Timer set to maybe 30ms. The speed mapping is in a function of its own.
flowLayoutPanel1.MouseMove += MouseScroll;
this.flowLayoutPanel1.MouseLeave += outSideCheck;
flowLayoutPanel1.AutoScroll = false;
int near = 33;
Point lastLocation = Point.Empty;
int dX = 0;
int dY = 0;
private void MouseScroll(object sender, MouseEventArgs e)
{
Point mouse = panel1.PointToClient(MousePosition);
Rectangle C = panel1.ClientRectangle;
// mouseMove has a bug, we need to workaround
if (mouse == lastLocation) return;
if (lastLocation == Point.Empty) { lastLocation = mouse; return; }
// distance from each edge
int dLeft = mouse.X - C.Left;
int dTop = mouse.Y - C.Top;
int dRight = C.Right - mouse.X;
int dBottom = C.Bottom - mouse.Y;
// relevant distances with sign
dX = dLeft < near ? dLeft : dRight < near ? -dRight : 0;
dY = dTop < near ? dTop : dBottom < near ? -dBottom : 0;
// we need the closest edge to check if we are moving in or out
List<int> edges = new List<int>() { dLeft, dTop, dRight, dBottom };
var closest = edges.IndexOf(edges.Min());
// if we are moving
if (dX != 0 | dY != 0)
// if moving out: go else stop going
if (!movingIn(mouse, closest)) timer1.Start(); else timer1.Stop();
// remember position
lastLocation = mouse;
}
bool movingIn(Point current, int Edge)
{
switch (Edge)
{
case 0: return current.X > lastLocation.X;
case 1: return current.Y > lastLocation.Y;
case 2: return current.X < lastLocation.X;
case 3: return current.Y < lastLocation.Y;
}
return false;
}
void scrollFLP(int deltaX, int deltaY)
{
flowLayoutPanel1.Left += getSpeedFromDistance(deltaX);
flowLayoutPanel1.Top += getSpeedFromDistance(deltaY);
Size C = panel1.ClientSize;
if (flowLayoutPanel1.Left > 1) { flowLayoutPanel1.Left = 0; timer1.Stop(); }
if (flowLayoutPanel1.Right < C.Width)
{ flowLayoutPanel1.Left = C.Width - flowLayoutPanel1.Width; timer1.Stop(); }
if (flowLayoutPanel1.Top > 1) { flowLayoutPanel1.Top = 0; timer1.Stop(); }
if (flowLayoutPanel1.Bottom < C.Height)
{ flowLayoutPanel1.Top = C.Height - flowLayoutPanel1.Height; timer1.Stop(); }
}
int getSpeedFromDistance(int delta)
{
int sig = Math.Sign(delta);
int d = Math.Abs(delta);
if (d > near / 2) return sig;
else if (d > near / 3) return 2 * sig;
else if (d > near / 4) return 4 * sig;
else if (d > near / 5) return 6 * sig;
else return 10 * sig;
}
private void timer1_Tick(object sender, EventArgs e)
{
if (insidePanel()) scrollFLP(dX, dY); else timer1.Stop();
}
bool insidePanel()
{
return panel1.ClientRectangle.Contains(panel1.PointToClient(MousePosition));
}
private void outSideCheck(object sender, EventArgs e)
{
if (!insidePanel()) {timer1.Stop(); lastLocation = Point.Empty;}
}
Of course you'll want to play with the various 'magic' numbers :-)
Stop code and direction check are now included.
As usual, key is to know precisely what you want.. I hope this gets you started on ways to achieve it!
It's been a while since this question was asked. I just encountered the problem. My scenario was a little different, but I still think it's a solution to the same problem (at worst a timer control can be used because autoscroll is not turned on).
Here is my scenario: I have one panel control (normal panel). I have an PictureBox in it that I made with zoom. I'm making rectangular selections on top of this image, and when the selections spilled out of the panel, my panel was supposed to slide in the direction I was selecting. (in my scenario, mouse is pressed)(also in my scenario, autoscroll is on). This is how I solved it without writing so much code:
I added two private variable for scroll position (Valid for the whole class scope).
private int xPos;
private int yPos;
private int speed = 5;
and I assigned them the current scroll positions when the form is loaded.
private void Form1_Load(object sender, EventArgs e)
{
//when I change the scrollbar manually or change with zoom I still
//need to add these lines to the related event
xPos = panel1.HorizontalScroll.Value;
yPos = panel1.VerticalScroll.Value;
}
and inside my picturebox's mousemove event
private void picturebox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left) {
Point mouse = panel1.PointToClient(MousePosition);
if (!panel1.ClientRectangle.Contains( mouse ))
{
Rectangle CRect = panel1.ClientRectangle;
int dLeft = mouse.X - CRect.Left;
int dRight = CRect.Right - mouse.X;
int dTop = mouse.Y - CRect.Top;
int dBottom = CRect.Bottom - mouse.Y;
if(dLeft < 0 && panel1.HorizontalScroll.Value > 0)
{
xPos = -panel1.AutoScrollPosition.X - speed;
}
if (dRight < 0 && panel1.HorizontalScroll.Value < panel1.HorizontalScroll.Maximum)
{
xPos = -panel1.AutoScrollPosition.X + speed;
}
if (dTop < 0 && panel1.VerticalScroll.Value > 0)
{
yPos = -panel1.AutoScrollPosition.Y - speed;
}
if (dBottom < 0 && panel1.VerticalScroll.Value < panel1.VerticalScroll.Maximum)
{
yPos = -panel1.AutoScrollPosition.Y + speed;
}
panel1.AutoScrollPosition = new Point(xPos, yPos);
}
}
}
I have a scrollviewer with a grid as the child. I am changing the grid's width and height properties to show different "zoom" levels. The grid contains 2 rows with many columns of images, all the same size.
However, I want the relative position of the scrollbar to stay the same. Whatever is on the center of the screen should still be on the center of the screen after changing the grid's size.
Default "zoomed in" view:
private void SizeGrid()
{
grid1.Width = (scrollViewer1.ViewportWidth / 2) * grid1.ColumnDefinitions.Count;
grid1.Height = (scrollViewer1.ViewportHeight / 2) * grid1.RowDefinitions.Count;
}
"Zoomed out" view:
private void scrollViewer1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyboardDevice.IsKeyDown(Key.Insert))
{
grid1.Width = (scrollViewer1.ViewportWidth / 2) * grid1.ColumnDefinitions.Count / 5;
grid1.Height = (scrollViewer1.ViewportHeight / 2) * grid1.RowDefinitions.Count / 3;
}
}
What I tried doing...
If I know what column is focused (I don't want to need to know this):
double shiftAmount = (scrollViewer1.ScrollableWidth / (grid1.ColumnDefinitions.Count - columnsOnScreen));
scrollViewer1.ScrollToHorizontalOffset(column * shiftAmount);
If I don't know exactly what column they are looking at, but I just want to keep the relative position...
double previousScrollRatio = scrollViewer1.HorizontalOffset / scrollViewer1.ScrollableWidth;
//resize grid...
scrollViewer1.ScrollToHorizontalOffset(previousScrollRatio * scrollViewer1.ScrollableWidth);
Neither approach works. If I zoom out with the scrollbar centered, then the scrollbar will go to the far right. Any idea?
A minimal code example can be found here plus the scroll_KeyDown method from above.
Screenshot of the default zoom:
Screenshot after zooming out, incorrectly (the navy blue and pink squares are far off screen):
Screenshot after zooming out, what it should look like:
Here is a solution to keep the content in center while zooming in or out
//variables to store the offset values
double relX;
double relY;
void scrollViewer1_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ScrollViewer scroll = sender as ScrollViewer;
//see if the content size is changed
if (e.ExtentWidthChange != 0 || e.ExtentHeightChange != 0)
{
//calculate and set accordingly
scroll.ScrollToHorizontalOffset(CalculateOffset(e.ExtentWidth, e.ViewportWidth, scroll.ScrollableWidth, relX));
scroll.ScrollToVerticalOffset(CalculateOffset(e.ExtentHeight, e.ViewportHeight, scroll.ScrollableHeight, relY));
}
else
{
//store the relative values if normal scroll
relX = (e.HorizontalOffset + 0.5 * e.ViewportWidth) / e.ExtentWidth;
relY = (e.VerticalOffset + 0.5 * e.ViewportHeight) / e.ExtentHeight;
}
}
private static double CalculateOffset(double extent, double viewPort, double scrollWidth, double relBefore)
{
//calculate the new offset
double offset = relBefore * extent - 0.5 * viewPort;
//see if it is negative because of initial values
if (offset < 0)
{
//center the content
//this can be set to 0 if center by default is not needed
offset = 0.5 * scrollWidth;
}
return offset;
}
idea behind is to store the last scroll position and use it to calculate the new offset whenever the content size is changed which will make the change in extent.
just attach the event ScrollChanged of ScrollViewer to this event handler in constructor etc. and leave the rest to it.
eg
scrollViewer1.ScrollChanged += scrollViewer1_ScrollChanged;
above solution will ensure to keep the grid in center, even for first load
sample of centered content
zoomed in
zoomed out
Extra
I also tried to create an attachable behavior for the same so you do not need to wire the events, just setting up the property will enable or disable the behavior
namespace CSharpWPF
{
public class AdvancedZooming : DependencyObject
{
public static bool GetKeepInCenter(DependencyObject obj)
{
return (bool)obj.GetValue(KeepInCenterProperty);
}
public static void SetKeepInCenter(DependencyObject obj, bool value)
{
obj.SetValue(KeepInCenterProperty, value);
}
// Using a DependencyProperty as the backing store for KeepInCenter. This enables animation, styling, binding, etc...
public static readonly DependencyProperty KeepInCenterProperty =
DependencyProperty.RegisterAttached("KeepInCenter", typeof(bool), typeof(AdvancedZooming), new PropertyMetadata(false, OnKeepInCenterChanged));
// Using a DependencyProperty as the backing store for Behavior. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BehaviorProperty =
DependencyProperty.RegisterAttached("Behavior", typeof(AdvancedZooming), typeof(AdvancedZooming), new PropertyMetadata(null));
private static void OnKeepInCenterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollViewer scroll = d as ScrollViewer;
if ((bool)e.NewValue)
{
//attach the behavior
AdvancedZooming behavior = new AdvancedZooming();
scroll.ScrollChanged += behavior.scroll_ScrollChanged;
scroll.SetValue(BehaviorProperty, behavior);
}
else
{
//dettach the behavior
AdvancedZooming behavior = scroll.GetValue(BehaviorProperty) as AdvancedZooming;
if (behavior != null)
scroll.ScrollChanged -= behavior.scroll_ScrollChanged;
scroll.SetValue(BehaviorProperty, null);
}
}
//variables to store the offset values
double relX;
double relY;
void scroll_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ScrollViewer scroll = sender as ScrollViewer;
//see if the content size is changed
if (e.ExtentWidthChange != 0 || e.ExtentHeightChange != 0)
{
//calculate and set accordingly
scroll.ScrollToHorizontalOffset(CalculateOffset(e.ExtentWidth, e.ViewportWidth, scroll.ScrollableWidth, relX));
scroll.ScrollToVerticalOffset(CalculateOffset(e.ExtentHeight, e.ViewportHeight, scroll.ScrollableHeight, relY));
}
else
{
//store the relative values if normal scroll
relX = (e.HorizontalOffset + 0.5 * e.ViewportWidth) / e.ExtentWidth;
relY = (e.VerticalOffset + 0.5 * e.ViewportHeight) / e.ExtentHeight;
}
}
private static double CalculateOffset(double extent, double viewPort, double scrollWidth, double relBefore)
{
//calculate the new offset
double offset = relBefore * extent - 0.5 * viewPort;
//see if it is negative because of initial values
if (offset < 0)
{
//center the content
//this can be set to 0 if center by default is not needed
offset = 0.5 * scrollWidth;
}
return offset;
}
}
}
enabling the behavior
via xaml
<ScrollViewer l:AdvancedZooming.KeepInCenter="True">
or
<Style TargetType="ScrollViewer" x:Key="zoomCenter">
<Setter Property="l:AdvancedZooming.KeepInCenter"
Value="True" />
</Style>
or via code like
scrollViewer1.SetValue(AdvancedZooming.KeepInCenterProperty, true);
or
AdvancedZooming.SetKeepInCenter(scrollViewer1, true);
set the property l:AdvancedZooming.KeepInCenter="True" inline, via styles or programmatically in order to enable the behavior on any scrollviewer
l: refers to the namespace to AdvancedZooming class xmlns:l="clr-namespace:CSharpWPF" in this example
This may sound a bit complicated but if you align the center of the object (that is inside the grid's cell) with the center of the scrollbar, using the screen coordinates?
You can use the PointToScreen to get the object coordinates in screen coordinates and align it after the zoom out or zoom in.
private void OnMouseMove(object sender, MouseEventArgs e)
{
var element = (UIElement)e.Source;
int c = Grid.GetColumn(element);
int r = Grid.GetRow(element);
}
This only works, if you have UIElements in each cell.
Or you can try this to get the coordinates.
This is just an assumption since I cannot test it right know.
I have a ListView (auftraegeView). Of this ListView I whish to change the FontSize of its Items through Ctrl + MouseWheel aka. a simple zoom like in excel or a browser.
In the form's ctor I subscribed my method to the event
this.MouseWheel += scrollZoom;
My EventHandler calculates the new FontHeight and applies it, if it doesn't exceed the bounds. The RowHeight is always kept a little bigger, finally I resize the columns so the zoom also works on the horizontal scale.
private void scrollZoom(object sender, MouseEventArgs e)
{
if(Control.ModifierKeys != Keys.Control)
return;
int currFontHeight = ListViewFontHeight;
int delta = (e.Delta)/120;
int newFontHeight = currFontHeight + delta;
if(newFontHeight < 1 || newFontHeight > 150)
return;
ListViewFontHeight = newFontHeight;
ListViewRowHeight = ListViewFontHeight + 4;
auftraegeView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
}
ListViewFontHeight gets the Font.Height of the first Item. (Value is identical across all Items, so the first is as good as any.)
The set is where the issue seems to be (see below). My idea is that I just go through each Item and change the Font.
private int ListViewFontHeight
{
get { return auftraegeView.Items[0].Font.Height; }
set
{
foreach (ListViewItem line in auftraegeView.Items)
{
line.Font = new Font(line.Font.FontFamily, value);
}
}
}
ISSUE / QUESTION
Regardless of the direction I scroll in, the FontSize only increases till it hits the ceiling. The rest works fine (setting ListViewRowHeight, detecting the event at all,...).
What might be causing this?
Try this:
delta = (e.Delta > 0? 1 : -1);
to be on the safe side for different mouse settings.
This works for me:
float delta = (e.Delta > 0 ? 2f : -2f);
listView1.Font = new Font (listView1.Font.FontFamily, listView1.Font.Size + delta);
Found it myself:
In the ListViewFontHeight - property the get accessor used Item.Font.Height instead of Item.Font.Size
private int ListViewFontHeight
{
get { return (int)auftraegeView.Items[0].Font.Size; } //works now