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

Issue

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.

Solution

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');
        }
      )),
    );
  }
}

Answered By – Christopher Moore

Answer Checked By – Robin (FlutterFixes Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *