I need to know if a key was down (pressed) while the user clicked on a button

801

This can be done with a FocusNode.

You'll need a stateful widget where you can use initialize the node. You need to attach the node and define the callback that is called on keyboard presses. Then you can request focus from the node with requestFocus so that the node receives the keyboard events.

You'll also need to call _nodeAttachment.reparent(); in your build method. You should also dispose the node in dispose.

The example below prints true or false for whether the shift key is pressed when the button is pressed. This can be easily expanded to other keys like control and alt with the isControlPressed and isAltPressed properties.


Full example:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late final FocusNode focus;
  late final FocusAttachment _nodeAttachment;
  
  bool isShiftPressed = false;
  
  @override
  void initState() {
    super.initState();
    focus = FocusNode(debugLabel: 'Button');
    _nodeAttachment = focus.attach(context, onKey: (node, event) {
      isShiftPressed = event.isShiftPressed;
    });
    focus.requestFocus();
  }
  
  @override
  void dispose() {
    focus.dispose();
    super.dispose();
  }
  
  Widget build(BuildContext context) {
    _nodeAttachment.reparent();
    return TextButton(
      onPressed: () {
        print(isShiftPressed);
      },
      child: Text('Test'),
    );
  }
}

You can still use this solution for your more specific problem. Wrap the above example around your list of checkboxes. You can do a bit of simple logic to get your intended behavior. If what I have here is not exact, you should be able to easily modify it to your needs. This proves that you can use this method for your need, however, even if some details in the logic are not exact:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late final FocusNode focus;
  late final FocusAttachment _nodeAttachment;
  
  bool isShiftPressed = false;
  
  List<bool> checkboxStates = List.filled(5, false);
  
  int lastClicked = -1;
  
  @override
  void initState() {
    super.initState();
    focus = FocusNode(debugLabel: 'Button');
    _nodeAttachment = focus.attach(context, onKey: (node, event) {
      isShiftPressed = event.isShiftPressed;
    });
    focus.requestFocus();
  }
  
  @override
  void dispose() {
    focus.dispose();
    super.dispose();
  }
  
  Widget build(BuildContext context) {
    _nodeAttachment.reparent();
    return Column(
      children: List.generate(checkboxStates.length, (index) => Checkbox(
        value: checkboxStates[index],
        onChanged: (val) {
          if(val == null) {
            return;
          }
          
          setState(() {            
            if(isShiftPressed && val) {
              if(lastClicked >= 0) {
                bool loopForward = lastClicked < index;
                if(loopForward) {
                  for(int x = lastClicked; x < index; x++) {
                    checkboxStates[x] = true;
                  }
                }
                else {
                  for(int x = lastClicked; x > index; x--) {
                    checkboxStates[x] = true;
                  }
                }
              }
            }
            checkboxStates[index] = val;
          });
          
          if(val) {
            lastClicked = index;
          }
          else {
            lastClicked = -1;
          }
          
          print('Checkbox $index: $isShiftPressed');
        }
      )),
    );
  }
}
Share:
801
D. Joe
Author by

D. Joe

Updated on December 29, 2022

Comments

  • D. Joe
    D. Joe over 1 year

    In a Flutter Desktop app, I want to know if, when a user clicks on a button with the mouse, they were also holding down a key (like Shift, Control, Alt etc).

    How can this be done?

    EDIT

    My initial question wasn't clear enough.

    I have a dynamic list of checkboxes and I want to use SHIFT+click to select everything between the last selected one and the one that was selected with SHIFT down.

    I have looked at FocusNode but that seems to only work for 1 element.

  • D. Joe
    D. Joe almost 3 years
    Thank you very much for your reply. I realize now I must have not stated my problem clearly enough. Please see the edit in my original question.
  • D. Joe
    D. Joe almost 3 years
    yes, saw your edit but didn't yet have the time to test it. I will let you know as soon as I implement it. Thanks.
  • D. Joe
    D. Joe almost 3 years
    Finally got the time to try this out, and it worked. Thanks. Marked your answer as the correct one.