3

I found a strange behavior with VScrollBar (vertical scrollbar available in Visual Studio tool box). The problem is "if I swipe down on the scrollbar, it moves up. If I swipe up, it moves down".

Steps to replicate Bug or behavior - 1

1) Add VScrollBar as a child to any user control.

2) Swipe up or down on the user control (not on scrollbar). Vertical scrollbar moves in opposite direction even if there isn't any programmatical connection between content and VScrollBar

Steps to replicate Bug or behavior - 2

1) Add VScrollBar as a child to any user control.

2) Swipe on scrollbar, it will move up during swipe up and down during swipe down (correct behavior)

3) Swipe up or down on the user control. Vertical scrollbar moves in opposite direction

4) Now swipe up or down on the vertical scrollbar. Vertical scrollbar starts moving in opposite direction (Buggy behavior, happens only after bug no: 1)

enter image description here

Simple control with vertical scrollbar to replicate this behavior

public class QuickViewer : Control
{
    public QuickViewer()
    {
        // Designer generated code
        // Copy pasted for illustration alone

        this.vScrollBar1 = new System.Windows.Forms.VScrollBar();
        this.SuspendLayout();
        // 
        // vScrollBar1
        // 
        this.vScrollBar1.Location = new System.Drawing.Point(420, 4);
        this.vScrollBar1.Name = "vScrollBar1";
        this.vScrollBar1.Size = new Size(this.vScrollBar1.Width, 292);            
        // 
        // QuickViewer
        //             
        this.Controls.Add(this.vScrollBar1);
        this.Name = "QuickViewer";
        this.Size = new System.Drawing.Size(441, 296);
        this.vScrollBar1.Value = 5;        
        this.ResumeLayout(false);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        //My actual control is different. I prepared a simple control to replicate the buggy behavior of VScrollBar

        //Control border
        Pen borderPen = new Pen(Color.LawnGreen, 5);
        e.Graphics.DrawRectangle(borderPen, ClientRectangle);
        borderPen.Dispose();

        //View area
        Rectangle rect = new Rectangle(ClientRectangle.Location, ClientRectangle.Size);
        rect.Inflate(-25, -10);
        e.Graphics.FillRectangle(Brushes.White, rect);
        e.Graphics.DrawRectangle(Pens.Black, rect);
        this.Font = new System.Drawing.Font("Segoe UI", 12, FontStyle.Bold);
        StringFormat format = new StringFormat() { Alignment = StringAlignment.Center };
        e.Graphics.DrawString("Quick viewer", this.Font, Brushes.Black, rect, format);
        string content = "This is a control created to illustrate the bug in VScrollBar." +
            "\n Control area refers to the area with white background" +
            "\n Control and Vertical Scrollbar are not programatically connected with each other."
            + "But still VScrollBar moves if you swipe on control area";

        Font font = new System.Drawing.Font("Segoe UI", 12, FontStyle.Italic);
        rect.Y += 20;
        e.Graphics.DrawString(content, font, Brushes.Black, rect, format);

        font.Dispose();
        format.Dispose();
        base.OnPaint(e);
    }

    /// <summary> 
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary> 
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }


    private System.Windows.Forms.VScrollBar vScrollBar1;

}

Question:

Is there any way to overcome this behavior or bug ? I want the scrollbar to move down while swiping down and move up while swiping up. There should not be any scrolling when swiping over the content

Louis
  • 146,715
  • 28
  • 274
  • 320
Kira
  • 1,403
  • 1
  • 17
  • 46

2 Answers2

4

I want the scrollbar to move down while swiping down and move up while swiping up.

As per Hans Passants comment its just a system setting (in the form of a registry key):

enter image description here

The answer is actually over at SuperUser:

https://superuser.com/questions/310681/inverting-direction-of-mouse-scroll-wheel

In C# as you wanted:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32;
using System.Diagnostics;
using System.Security.Principal;

namespace WindowsFormsApplication1 {

    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }
        private Flippable[] flippable;
        private void Form1_Load(object sender, EventArgs e) {
            WindowsPrincipal pricipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
            bool hasAdministrativeRight = pricipal.IsInRole(WindowsBuiltInRole.Administrator);

            if (!hasAdministrativeRight) {
                RunElevated(Application.ExecutablePath);
                this.Close();
                Application.Exit();
            }

            //probably only want to flip mice.
            flippable = getFlippable("hid.mousedevice");
            dgv_flippable.DataSource = flippable;
            foreach (var col in dgv_flippable.Columns.OfType<DataGridViewCheckBoxColumn>()) {
                col.TrueValue = true;
                col.FalseValue = false;
                col.IndeterminateValue = null;
            }
        }
        private static bool RunElevated(string fileName)
        {
            //MessageBox.Show("Run: " + fileName);
            ProcessStartInfo processInfo = new ProcessStartInfo();
            processInfo.UseShellExecute = true;
            processInfo.Verb = "runas";
            processInfo.FileName = fileName;
            try
            {
                Process.Start(processInfo);
                return true;
            }
            catch (Win32Exception)
            {
                //Do nothing. Probably the user canceled the UAC window
            }
            return false;
        }

        private Flippable[] getFlippable(string filter) {
            List<Flippable> flips = new List<Flippable>();
            using (RegistryKey hid = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Enum\HID\",false)) {
                foreach (string devicekn in hid.GetSubKeyNames()) {
                    using (RegistryKey device = hid.OpenSubKey(devicekn,false)) {
                        foreach (string devicekn2 in device.GetSubKeyNames()) {
                            using (RegistryKey device2 = device.OpenSubKey(devicekn2,false)) {
                                using (RegistryKey devparam = device2.OpenSubKey("Device Parameters",true)) {
                                    if (devparam != null) {
                                        flips.Add(new Flippable(new string[] { devicekn, devicekn2 }, device2, devparam, tmr_popup));
                                    }
                                }
                            }
                        }
                    }
                }
            }
            if (filter != null) {
                return flips.Where(f=>f.name.Contains(filter)).ToArray();
            }
            return flips.ToArray();
        }

        private void dgv_flippable_MouseUp(object sender, MouseEventArgs e) {
            dgv_flippable.EndEdit();
        }

        private void button1_Click(object sender, EventArgs e) {
            flippable = getFlippable(null);
            dgv_flippable.DataSource = flippable;
        }

        private void btn_flip_Click(object sender, EventArgs e) {
            foreach (var f in flippable) {
                f.vertical = true;
                f.horizontal = true;
            }
            dgv_flippable.DataSource = null;
            dgv_flippable.DataSource = flippable;
        }

        private void btn_normal_Click(object sender, EventArgs e) {
            foreach (var f in flippable) {
                f.vertical = false;
                f.horizontal = false;
            }
            dgv_flippable.DataSource = null;
            dgv_flippable.DataSource = flippable;
        }

        private void tmr_popup_Tick(object sender, EventArgs e) {
            tmr_popup.Enabled = false;
            notifyIcon1.ShowBalloonTip(99999999);
        }
    }

    public class Flippable {
        public Flippable(string[] keyPath, RegistryKey deviceKey, RegistryKey devparam, Timer timer) {
            this._keyPath = keyPath;
            IEnumerable<bool?> flipValues = Flippable.valueNames
                .Select(v => onlyIntBool(devparam.GetValue(v, null)));
            this.name = (string)deviceKey.GetValue("DeviceDesc");
            this._vertical = flipValues.ElementAt(0);
            this._horizontal = flipValues.ElementAt(1);
            this._timer = timer;
        }
        private bool? onlyIntBool(object value) {
            try {
                return value == null ? null : (bool?)(((int)value) != 0);
            } catch {
                return null;
            }
        }
        public static string[] valueNames = new string[] { "FlipFlopWheel", "FlipFlopHScroll" };

        public string name { get; private set; }
        private string[] _keyPath;
        private bool? _vertical;
        private bool? _horizontal;
        Timer _timer;
        public bool? vertical { set { flip(Flippable.valueNames[0], value); _vertical = value; } get { return _vertical; } }
        public bool? horizontal { set { flip(Flippable.valueNames[1], value); _horizontal = value; } get { return _horizontal; } }

        public void flip(string valueName, bool? value) {
            using (RegistryKey hid = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Enum\HID\", false)) {
                using (RegistryKey device = hid.OpenSubKey(_keyPath[0], false)) {
                    using (RegistryKey device2 = device.OpenSubKey(_keyPath[1], false)) {
                        using (RegistryKey devparam = device2.OpenSubKey("Device Parameters", true)) {
                            if (value == null) {
                                devparam.DeleteValue(valueName);
                            } else {
                                devparam.SetValue(valueName, value == true ? 1 : 0);
                                _timer.Enabled = true;
                            }
                        }
                    }
                }
            }
        }

    }
}

REF: https://github.com/jamie-pate/flipflop-windows-wheel/blob/master/Form1.cs

Disclaimer: normally this question would get closed as a duplicate but because there is a bounty on it and the duplicate is over at SuperUser I've chosen to share that answer here. Full credit to the original author: https://superuser.com/users/108033/richard and https://superuser.com/users/132069/jamie-pate

Community
  • 1
  • 1
Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
  • Cool, give it a go and let me know how you go :) – Jeremy Thompson Aug 26 '16 at 03:59
  • I tried this application but still no luck. Also, this is not related to a system setting. I also posted my question in MSDN about this behavior https://social.msdn.microsoft.com/Forums/en-US/981f8b1c-9f7b-4a6f-9bda-4327703a3492/vertical-scrollbar-behavior-with-touch-screen?forum=winforms. – Kira Aug 26 '16 at 04:40
  • 1
    The setting in the registry is a ***global*** setting. Your application should not tamper with it. If the user prefers to have her computer scroll in a certain way, you should respect that. The purpose of Hans's comment was to demonstrate that this is not a bug in Windows, but rather a configurable setting (as it is on the Mac OS). Let the user decide. – Cody Gray - on strike Aug 26 '16 at 07:15
  • @CodyGray I agree, you can flip it on App.Activate and off in LostFocus. It's not me wanting to do it mate. You're welcome to put an answer here saying that is completely unorthodox - I won't down vote it as "not a solution". – Jeremy Thompson Aug 26 '16 at 07:22
  • 2
    There's no guarantee that business about flipping it on and off will actually work. Lots of registry settings are cached, and modifying them on the fly may not actually cause them to take effect. You are also dead out if your app crashes, failing to restore the setting. Not to mention the issue of overriding user preferences. This is classically bad programming practice, which makes your answer bad advice. Nothing personal; I downvoted it and left a comment on the same grounds that I would if I saw someone recommending a call to `DoEvents` or something vulnerable to SQL injection. – Cody Gray - on strike Aug 26 '16 at 10:30
  • Hey guys can you continue this in chat or under the question, thanks – Jeremy Thompson Aug 26 '16 at 10:31
  • @CodyGray I mentioned I agreed with you, I'm not consulting here, I'm trained in Break/Fix scenarios. You should post your advice as an answer. It's a fine line and I'll give you an example - do you help hackers? Well no of course not, right? But every so often something will come up and you find yourself ref'n Jeff Atwood Coding Horror blog posts - I feel bad about this answer now but meh it's on the interwebs (without me): http://stackoverflow.com/questions/7143541/how-to-hide-c-sharp-application-from-taskmanager-processtab – Jeremy Thompson Aug 26 '16 at 10:36
3

I think what you want is a ViewPort.

Essentially you put a Control inside a PictureBox. The Control has a larger height than the PictureBox making it a ViewPort.

Before

enter image description here

You'll need to change the form designer code to get the control inside the PictureBox:

'
'PictureBox1
'
Me.PictureBox1.Location = New System.Drawing.Point(96, 87)
Me.PictureBox1.Name = "PictureBox1"
Me.PictureBox1.Size = New System.Drawing.Size(231, 195)
Me.PictureBox1.TabIndex = 0
Me.PictureBox1.TabStop = False
'
'VScrollBar1
'
Me.VScrollBar1.Location = New System.Drawing.Point(330, 88)
Me.VScrollBar1.Name = "VScrollBar1"
Me.VScrollBar1.Size = New System.Drawing.Size(34, 194)
Me.VScrollBar1.TabIndex = 2
'
'TextBox1
'
Me.TextBox1.Location = New System.Drawing.Point(0, 0)
Me.TextBox1.Multiline = True
Me.TextBox1.Name = "TextBox1"
Me.TextBox1.Size = New System.Drawing.Size(211, 251)
Me.TextBox1.TabIndex = 3
'
'Form1
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(522, 392)
Me.Controls.Add(Me.VScrollBar1)
Me.Controls.Add(Me.PictureBox1)
'======= THIS IS THE CRITICAL CHANGE ======= 
PictureBox1.Controls.Add(Me.TextBox1)

After

enter image description here

Then manually place a ScrollBar to the right of the PictureBox and facilitate the behaviour yourself, eg:

//set the VScroll the difference
VScroll.Max = Textbox.Height - PictureBox.Height;

In the VScroll event:

TextBox.Top = -VScroll.Value;

This will save you from mucking around with system settings in order to produce a QuickViewer custom control.

You can add smarts to it like programming PictureBox drag events to set the ScrollBar (and subsequently the inside controls Top). For most inside controls you'll just need to work out the Height which is easy using a for loop, eg:

foreach(var ctrl in PictureBox.Controls) { 
// tally up the controls height
...

For inside Textbox controls you can work out the Height based on Fontsize and number of lines. There are plenty of examples online showing how to do that. Since you're doing Textbox's with graphics, eg e.Graphics.DrawString it should be easy enough having the inside control as a innerPictureBox.

To swap the scroll/swipe direction set the VScroll default starting Value to its Max value and set the inside controls Top = VScroll.Value (no minus sign needed)

Community
  • 1
  • 1
Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321