I am trying to determine the percentage value of a scroll bar in response to its OnScroll event, though I am having a difficult time determining the actual percentage. I would figure it should be as simple as doing the ratio of the current scroll value divided by the maximum value. Though this produces odd results.
void ThePanel_Scroll(object sender, ScrollEventArgs e)
{
if (e.ScrollOrientation == ScrollOrientation.VerticalScroll)
{
double scrollPercent = (double)e.NewValue / (double)ThePanel.VerticalScroll.Maximum;
Console.Out.WriteLine("Max: " + ThePanel.VerticalScroll.Maximum + " Min: " + ThePanel.VerticalScroll.Minimum);
Console.Out.WriteLine("OldVal: " + e.OldValue + " NewVal: " + e.NewValue + " Val: " + ThePanel.VerticalScroll.Value);
Console.Out.WriteLine("CurPer: " + scrollPercent);
}
}
When running this code, and scrolling the bar all the way to the maximum position, I might get some output like so
Max: 529 Min: 0
OldVal: 170 NewVal: 170 Val: 170
CurPer: 0.321361058601134
Even though the scroll bar is at a maximum position its Value is not. I assume the reason is that the Value is based off of the top of the scroll bar. My problem is that I cannot determine the height/size of the scroll bar "bar" itself, to calculate a proper percentage.
Is the proper way to determine the percent based on using the height of the bar along with the Value, or am I missing something else? If it is based off the height, how to I determine that? Reading through the VScrollProperties MSDN there is nothing there that would tell me the height of the bar itself.
There is an inherent ambiguity in the scrollbar position due to the non-zero thumb height. Which side of the thumb do you consider the "active" side? Windows doesn't help you deal with that, it suddenly switches over from one side to another when the thumb gets close to the maximum position. If you don't compensate for that then you'll lose a portion of the range.
The workaround is to take the sting out that flipping behavior and apply your own effective maximum. Like this:
private void panel1_Scroll(object sender, ScrollEventArgs e) {
if (e.ScrollOrientation == ScrollOrientation.VerticalScroll) {
var max = panel1.VerticalScroll.Maximum - panel1.VerticalScroll.LargeChange;
var pos = Math.Min(max, e.NewValue);
var percentage = 100.0 * pos / max;
// etc...
}
}
You can calculate the vertical scroll position in percent like this:
int percent_v = (int) Math.Min(100f * container.VerticalScroll.Value /
(content.Height - container.ClientSize.Height), 100);
Here the Container would be the Panel and the content maybe a Picturebox or whatever else you have placed inside.
I looks a bit weird and it is. When scrolled to its maximum, the value is not the real maximum but is reduced by the containing controls's height.
For horizontal scrolling the same applies.
Disclaimer: this is what I found by testing; I never saw it documented anywhere.
Related
EDIT: I forgot to describe my actual probem :D After orientation changes, portrait -> landscape or vice versa, the thumbs do not layout correctly. I want a thumb thats in the middle (width / 2) of the layout to remain at the middle of the layout after orientation change.
I'm trying to create a "range slider".
What I have is 2 simple views, that represent the two thumbs and a frame layout that represents the slider.
I'm laying out my two thumbs in the overridden OnLayout method, based on the width of the slider multiplied by the value of a thumb (between 0 and 1).
public class RangeSliderView : ViewGroup
{
public RangeSliderView(Context context) : base(context)
{
var thumbL = new Thumb(context);
var thumbR = new Thumb(context);
AddView(thumbL);
AddView(thumbR);
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
if (changed)
{
//Get the width and height if not set yet
if (ThumbLeft.MeasuredWidth == 0 || ThumbLeft.MeasuredHeight == 0)
ThumbLeft.Measure(MeasureSpec.MakeMeasureSpec(0, MeasureSpecMode.Unspecified), MeasureSpec.MakeMeasureSpec(b, MeasureSpecMode.AtMost));
if (ThumbRight.MeasuredWidth == 0 || ThumbRight.MeasuredHeight == 0)
ThumbRight.Measure(MeasureSpec.MakeMeasureSpec(0, MeasureSpecMode.Unspecified), MeasureSpec.MakeMeasureSpec(b, MeasureSpecMode.AtMost));
//calculate width and the relative position of the thumbs
int width = r - l;
int thumbLeftX = (int)(width * lastProgressLeft); //last progresses are a value between 0 and 1
int thumbRightX = (int)(width * lastProgressRight);
//position the thumbs
ThumbLeft.Layout(l: l + thumbLeftX,
t: t,
r: l + thumbLeftX + ThumbLeft.MeasuredWidth,
b: ThumbLeft.MeasuredHeight);
ThumbRight.Layout(l: l + thumbRightX - ThumbRight.MeasuredWidth,
t: t,
r: l + thumbRightX,
b: ThumbLeft.MeasuredHeight);
}
}
}
The values really seem to be correct, in the landscape, the "r" value is bigger than in portrait. "l" is always 0 since the control is aligned to the left of my screen. The calculation seems correct, I tested moving one of the thumbs to the middle, so I can exactly see if thumbLeftX or thumbRightX are 50% of the width. It seemed correct.
I think the Layout(...) calls on the thumbs do not layout my thumbs reliably.
Is there a layout call that I'm missing?
Do I need to call other methods to re-layout my thumbs correctly?
Initially, the lastProgressLeft is 0 and the lastProgressRight is 1 (meaning thumb left should be at the left end of the slider, the right thumb at the right end of the slider. This works great, also on orientation changes, it looks correct.
I have a windows universal app where I am rendering a scene with DirectX. I want to make use of the Scrollviewer and therefore I render my scene behind the Scrollviewer and want to calculate the scene transformation based on the Scrollviewer. It works fine so far, especially the translation and scrolling. But when I zoom in, the scene jumps around in two special situations:
The scene had enough space and was centered and now scrolling is required.
The opposite direction.
More or less I use the following code:
float zoom = scrollViewer.ZoomFactor;
float inverseZoom = 1f / scrollViewer.ZoomFactor;
float scaledContentW = Document.Size.X * scrollViewer.ZoomFactor;
float scaledContentH = Document.Size.Y * scrollViewer.ZoomFactor;
float translateX;
float translateY;
if (scaledContentW < scrollViewer.ViewportWidth)
{
translateX = ((float)scrollViewer.ViewportWidth * inverseZoom - Document.Size.X) * 0.5f;
}
else
{
translateX = -inverseZoom * (float)scrollViewer.HorizontalOffset;
}
if (scaledContentH < scrollViewer.ViewportHeight)
{
translateY = ((float)scrollViewer.ViewportHeight * inverseZoom - Document.Size.Y) * 0.5f;
}
else
{
translateY = -inverseZoom * (float)scrollViewer.VerticalOffset;
}
float visibleX = inverseZoom * (float)scrollViewer.HorizontalOffset;
float visibleY = inverseZoom * (float)scrollViewer.VerticalOffset; ;
float visibleW = Math.Min(Document.Size.X, inverseZoom * (float)scrollViewer.ViewportWidth);
float visibleH = Math.Min(Document.Size.Y, inverseZoom * (float)scrollViewer.ViewportHeight);
Rect2 visibleRect = new Rect2(visibleX, visibleY, visibleW, visibleH);
transform =
Matrix3x2.CreateTranslation(
translateX,
translateY) *
Matrix3x2.CreateScale(zoom);
You can get an example here: https://github.com/SebastianStehle/Win2DZoomTest
To be sure that my eyes are not broken I was zooming around and have written the translation and zoom values to a file. You can see it here:
https://www.dropbox.com/s/9ak6ohg4zb1mnxa/Test.png?dl=0
The meaning of the columns is the following:
Column 1: The computed zoom value of the transformation matrix (M11) = ScrollViewer.ZoomFactor
Column 2: The computed x offset of the matrix (See above)
Column 3: The x value of the result of matrix * vector (500, 500), here: Colum1 * 500 + Column2
You see, that the matrix values look good, but when applying the transformation you get this little jump to the right for some milliseconds. One theory was, that the viewport might change because the scrollbar becomes visible. But this is not the case. I also tried fixed values here, made the scrollbars visible and even created a custom template for the scrollviewer with no scrollbars at all.
Btw: This is a cross post, I also asked the question here: https://github.com/Microsoft/Win2D/issues/125
You see this behavior because when you zoom bigger than the ScrollViewer's size, the zoom center point is moved. To fix this, you just need to subscribe to the ScrollViewer's LayoutUpdated event and inside the handler, manually keep its content in the center.
private void ScrollViewer_LayoutUpdated(object sender, object e)
{
this.ScrollViewer.ChangeView(this.ScrollViewer.ScrollableWidth / 2, this.ScrollViewer.ScrollableHeight / 2, this.ScrollViewer.ZoomFactor, true);
}
This should fix the jumpy movement on the two drawed Rectangles from Win2D.
Update
Just to prove my point, the jumpy behavior is most likely due to unusual translate x and/or y value change when the content size goes over the size of the ScrollViewer. So I wrote some code to log these values on the screen as shown below -
...
this.Test1.Text += ((float)translateX).ToString() + " ";
this.Test2.Text += ((float)translateY).ToString() + " ";
session.Transform =
Matrix3x2.CreateTranslation(
(float)translateX,
(float)translateY) *
Matrix3x2.CreateScale((float)zoom);
Now look at the numbers on the image above. What I did was I tried zooming in until the jumpy scene occurred. See the highlighted translate y value? It is slightly greater than its previous value, which is against the declining trend.
So to fix this, you will need to be able to skip these unusual values caused by ScrollViewer.
I need to represent a percent of value as a graph in DevExpress grid cell. I am able to paint using DrawLine but my problem is as soon as the percent value is equal to greater than 1 it is treated as 100% in this code. Please find the code below, As shown in the screenshot, 3.59 should be shown less than 8.35! Please help.
private void CustomDrawCell(object sender, RowCellCustomDrawEventArgs args)
{
args.Appearance.DrawBackground(args.Graphics, args.Cache, args.Bounds);
if (column != null)
{
int penSize = args.Bounds.Height * 2 / 3;
double value = GetValue(); // This is the value against which I have to display the graph, its in %.
int left = args.Bounds.Left;
int middle = args.Bounds.Height / 2 + args.Bounds.Top;
int width = args.Bounds.Width;
int right = (int)(left + width * value);
args.Graphics.DrawLine(new Pen(Color.Green, penSize), left, middle, right, middle);
}
args.Handled = true;
}
int right = (int)(left + width * value);
This code calculates the bar length correctly only if the value is between 0 and 1. If values are between 0 and 100 or between 0 and 10, you need to divide the result of multiplying by 100 or 10 correspondingly.
int right = (int)(left + width * value / 100);
By the way, it is not necessary to puzzle over custom drawing, because you are using XtraGrid. There is the RepositoryItemProgressBar component, which can be embedded into XtraGrid cell. It displays the line according to the cell value and allows you to define the maximum and minimum value, so that the line is most exactly visualize the cell value. Read this article to learn how to assign editors to columns: Assigning Editors to Columns and Card Fields
I am drawing a chart which I populate with the data I obtain from different procedures. I want to make two buttons to zoom in and out. I saw that I can use different functions from AxisX.ScaleView and I am playing a bit with those. I am almost there but I have a problem at the moment of drawing the chart:If you see the image 1, this is the chart after executing the different procedures and drawing it for the first time. When I do a zoom in and a zoom out, the last bars (Week 22 from image 2) are cut in half and doesn't go to its original size.
Does anyone have any idea how can I manipulate the start and end position for the Axis X in order to make the zoom? Does anyone know how to get the initian values of start and end of the Chart Area? I place the code of my function to make the zoom of the chart:
private void setSize(int zoom)
{
int blockSize = (Convert.ToInt32(tbZoom.Text) + zoom) / 100;
// set view range to [0,max]
chartReport.ChartAreas[0].AxisX.Minimum = 0;
chartReport.ChartAreas[0].AxisX.Maximum = chartReport.Series[0].Points.Count;
// enable autoscroll
chartReport.ChartAreas[0].CursorX.AutoScroll = true;
chartReport.ChartAreas[0].CursorX.IsUserSelectionEnabled = true;
// let's zoom to [0,blockSize] (e.g. [0,100])
chartReport.ChartAreas[0].AxisX.ScaleView.Zoomable = true;
chartReport.ChartAreas[0].AxisX.ScaleView.SizeType = DateTimeIntervalType.Number;
int actualHeight = chartReport.Height;
int actualWidth = chartReport.Width;
int position = 0;
int size = blockSize;
chartReport.ChartAreas[0].AxisX.ScaleView.Zoom(position, size);
// disable zoom-reset button (only scrollbar's arrows are available)
chartReport.ChartAreas[0].AxisX.ScrollBar.ButtonStyle = ScrollBarButtonStyles.SmallScroll;
// set scrollbar small change to blockSize (e.g. 100)
chartReport.ChartAreas[0].AxisX.ScaleView.SmallScrollSize = blockSize;
tbZoom.Text = (blockSize * 100).ToString();
}
Your first line is setting the maximum of the axis wrong: chartReport.ChartAreas[0].AxisX.Maximum = chartReport.Series[0].Points.Count; sets it to 22, when it really should be 23 (based on the first image).
If your data will always look like this, simply add 1:
chartReport.ChartAreas[0].AxisX.Maximum = chartReport.Series[0].Points.Count + 1;
Unfortunately, using the automatic min/max values won't give you the actual values until the chart is actually drawn. If your chart has few DataPoints this isn't a problem, as you can just call chartReport.Refresh(); or something similar and then get the values from the axes. But, if you have a lot of points, the Refresh() will take a long time which is undesirable. In my extensive use of the charts, I wound up setting the axis ranges myself so I have full control, rather than using the automatic min/max values.
I'm making a winforms app c#. The vertical scroll bar min value is at the top and max at the bottom, and scrolling down increases the value and vice versa. Is there a way to invert it, so that up is higher and down is lower.
You cannot actually "see" the value of the scroll bar just by looking at it, so, in other words, there is no actual difference between having min at the top, max at the bottom, and then just inverting the value when you access it:
private void ScrollBar_Scroll(object sender, ScrollEventArgs e)
{
// get the value (0 -> 100)
int value = scrollBar.Value;
// invert it (100 -> 0)
value = 100 - value;
// display it
someLabel.Text = value.ToString();
}
Of course, you can also override the VScrollBar class and add your own "inverted value" property:
public class InvertedScrollBar : VScrollBar
{
/// <summary>
/// Gets or sets the "inverted" scrollbar value.
/// </summary>
/// <value>The inverted value.</value>
public int InvertedValue
{
get
{
int offset = this.Value - this.Minimum;
return this.Maximum - offset;
}
set
{
int offset = this.Maximum - value;
this.Value = this.Minimum + offset;
}
}
}
Note that Maximum still has to be larger than Minimum when configuring it.
The values returned by the Value property of a ScrollBar go from scrollBar.Minimum to scrollBar.Maximum - scrollBar.LargeChange.
Thus if a scroll bar has Minimum of 5, Maximum of 15, and LargeChange (which doubles as the visible portion of the scrolling range) is 3, then the possible return values go from 5 to 12.
So, to invert the value, you actually want to use:
scrollBar.Minimum + scrollBar.Maximum - scrollBar.LargeChange - scrollBar.Value
(Normally you can think of Value as position of the left or top edge of the thumb. The formula above will give you the bottom edge of the thumb. If you still want the top edge (i.e. values going from 8 to 15 in the example above), then use:
scrollBar.Minimum + scrollBar.Maximum - scrollBar.Value