官方文档
https://www.jetbrains.org/intellij/sdk/docs/user_interface_components/popups.html
https://www.jetbrains.org/intellij/sdk/docs/tutorials/action_system/grouping_action.html
Github
https://github.com/kungyutucheng/my_gradle_plugin
参考
Intellij IDEA 插件开发 -- ListPopup
运行环境
macOS 10.14.5
IntelliJ idea 2019.2.4
1、ActionGroupPopupAction
最常用,创建一个普通的弹出菜单
ActionGroupPopupAction
package com.kungyu.popup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.ui.popup.*;
import org.jetbrains.annotations.NotNull;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/**
* @author wengyongcheng
* @since 2020/3/3 10:48 下午
*/
public class ActionGroupPopupAction extends AnAction implements ListSelectionListener, JBPopupListener {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
// 单选,action被选中可以触发对应的actionPerformed方法
DefaultActionGroup actionGroup = (DefaultActionGroup) ActionManager.getInstance().getAction("popupGroup");
ListPopup listPopup = JBPopupFactory.getInstance().createActionGroupPopup("popup",actionGroup,e.getDataContext(), JBPopupFactory.ActionSelectionAid.SPEEDSEARCH,false);
listPopup.showInFocusCenter();
listPopup.addListSelectionListener(this);
listPopup.addListener(this);
}
/**
* 值改变事件监听,通过上下键选择时,可以通过getFirstIndex获取第一个选择的元素的索引,通过getLastIndex可以获取最后一个选择的元素的索引
* @param e
*/
@Override
public void valueChanged(ListSelectionEvent e) {
int firstIndex = e.getFirstIndex();
int lastIndex = e.getLastIndex();
boolean valueIsAdjusting = e.getValueIsAdjusting();
System.out.println("firstIndex:" + firstIndex);
System.out.println("lastIndex:" + lastIndex);
System.out.println("valueIsAdjusting:" + valueIsAdjusting);
}
/**
* popup 监听弹出菜单关闭动作
* @param event
*/
@Override
public void onClosed(@NotNull LightweightWindowEvent event) {
JBPopup jbPopup = event.asPopup();
System.out.println("关闭popup");
jbPopup.cancel();
}
}
核心方法是:
public ListPopup createActionGroupPopup(@Nullable @Nls(capitalization = Nls.Capitalization.Title)String title,
@NotNull ActionGroup actionGroup,
@NotNull DataContext dataContext,
ActionSelectionAid selectionAidMethod,
boolean showDisabledActions)
参数selectionAidMethod
代表action动作的匹配方式,可选值如下:
-
NUMBERING
:action前带有数字序号,通过选择对应数字即可触发对应action -
ALPHA_NUMBERING
:同NUMBERING
,数字不够,字母来凑 -
SPEEDSEARCH
:快速查找,通过输入字母实现模糊匹配,大小写不敏感 -
MNEMONICS
:翻译是帮助记忆,不理解,实例也看不出来,求赐教
其中,showInBestPositionFor
代表由idea来选择最佳展示位置,也可以使用以下方法来自定义位置:
/**
* Shows the popup at the bottom left corner of the specified component.
*
* @param componentUnder the component near which the popup should be displayed.
*/
void showUnderneathOf(@NotNull Component componentUnder);
展示在某个组件的左下角
/**
* Shows the popup at the specified point.
*
* @param point the relative point where the popup should be displayed.
*/
void show(@NotNull RelativePoint point);
指定具体某个位置
/**
* Shows the popup in the center of the specified component.
*
* @param component the component at which the popup should be centered.
*/
void showInCenterOf(@NotNull Component component);
展示在某个组件的中间位置
/**
* Shows the popups in the center of currently focused component
*/
void showInFocusCenter();
展示在已获取焦点的组件的中间位置
/**
* Shows in best position with a given owner
*/
void show(@NotNull Component owner);
展示在某个组件的最佳位置
/**
* Shows the popup in the center of the active window in the IDEA frame for the specified project.
*
* @param project the project in which the popup should be displayed.
*/
void showCenteredInCurrentWindow(@NotNull Project project);
展示在当前项目的所在窗口的中间位置
注册Action
<action id="com.kungyu.popup.ActionGroupPopupAction" class="com.kungyu.popup.ActionGroupPopupAction" text="ActionGroupPopupAction" description="ActionGroupPopupAction">
<add-to-group group-id="PopupMenuActions" anchor="first"/>
<keyboard-shortcut first-keystroke="control alt 1" keymap="Mac OS X 10.5+"/>
</action>
<group id="popupGroup"/>
<action class="com.kungyu.popup.CustomFirstAction" id="com.kungyu.popup.CustomFirstAction" text="CustomFirstAction" description="CustomFirstAction">
<add-to-group group-id="popupGroup" anchor="first"/>
</action>
<action class="com.kungyu.popup.CustomSecondAction" id="com.kungyu.popup.CustomSecondAction" text="CustomSecondAction" description="CustomSecondAction">
<add-to-group group-id="popupGroup" anchor="after" relative-to-action="CustomFirstAction"/>
</action>
CustomFirstAction
package com.kungyu.popup;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.ui.Messages;
import org.jetbrains.annotations.NotNull;
/**
* @author wengyongcheng
* @since 2020/3/4 11:06 上午
*/
public class CustomFirstAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Messages.showMessageDialog("第一个action触发", "第一个action", Messages.getInformationIcon());
}
}
CustomSecondAction
package com.kungyu.popup;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.ui.Messages;
import org.jetbrains.annotations.NotNull;
/**
* @author wengyongcheng
* @since 2020/3/4 11:06 上午
*/
public class CustomSecondAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Messages.showMessageDialog("第二个action触发", "第二个action", Messages.getInformationIcon());
}
}
效果
按住快捷键后效果选中customFirstAction后效果
selectionAidMethod=NUMBERING效果
selectionAidMethod=SPEEDSEARCH效果
2、ComponentPopupAction
自定义弹出菜单样式
ComponentPopupAction
package com.kungyu.popup;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.ui.popup.*;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
/**
* @author wengyongcheng
* @since 2020/3/3 10:48 下午
*/
public class ComponentPopupAction extends AnAction implements ListSelectionListener, JBPopupListener {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
// 自定义popup样式
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
JTextField textField = new JTextField();
panel.add(textField, BorderLayout.NORTH);
JButton button = new JButton("提交");
panel.add(button, BorderLayout.CENTER);
ComponentPopupBuilder componentPopupBuilder = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, textField);
JBPopup popup = componentPopupBuilder.createPopup();
popup.showInBestPositionFor(e.getDataContext());
}
/**
* 上下键选择事件,仅仅是改变,而不是按住回车之后的选择
* @param e
*/
@Override
public void valueChanged(ListSelectionEvent e) {
int firstIndex = e.getFirstIndex();
int lastIndex = e.getLastIndex();
boolean valueIsAdjusting = e.getValueIsAdjusting();
System.out.println("firstIndex:" + firstIndex);
System.out.println("lastIndex:" + lastIndex);
System.out.println("valueIsAdjusting:" + valueIsAdjusting);
}
/**
* popup 关闭监听
* @param event
*/
@Override
public void onClosed(@NotNull LightweightWindowEvent event) {
JBPopup jbPopup = event.asPopup();
System.out.println("关闭popup");
jbPopup.cancel();
}
}
注册Action
<action id="com.kungyu.popup.ComponentPopupAction" class="com.kungyu.popup.ComponentPopupAction"
text="ComponentPopupAction" description="ComponentPopupAction">
<add-to-group group-id="PopupMenuActions" anchor="first"/>
<keyboard-shortcut first-keystroke="control alt 1" keymap="Mac OS X 10.5+"/>
</action>
效果
ComponentPopupAction然而,我们会发现,这个输入框是获取不到焦点的,这就很值得研究了,但是,没有什么是难得到我们的,下面我们一步步探究如何获取到输入框到焦点》?(此处,不得不吐槽一句:IntelliJ的插件开发资料是真的少啊,步步卡)
解决思路1:
试图让输入框手动获取焦点,修改代码,增加如下语句:
textField.grabFocus();
嗯,怀着期待到心情,重新运行,oh,上帝,他还是不行。
解决思路2:
从源码抓起,查看JBPopupFactory#createComponentPopupBuilder
,找到实现类的代码:
@NotNull
@Override
public ComponentPopupBuilder createComponentPopupBuilder(@NotNull JComponent content, JComponent preferableFocusComponent) {
return new ComponentPopupBuilderImpl(content, preferableFocusComponent);
}
继续跟踪ComponentPopupBuilderImpl
类:
public ComponentPopupBuilderImpl(@NotNull JComponent component, JComponent preferredFocusedComponent) {
myComponent = component;
myPreferredFocusedComponent = preferredFocusedComponent;
}
追踪变量myPreferredFocusedComponent
的引用,发现除了上述构造函数,只有另外一处使用:
@Override
@NotNull
public JBPopup createPopup() {
AbstractPopup popup = new AbstractPopup().init(
myProject, myComponent, myPreferredFocusedComponent, myRequestFocus, myFocusable, myMovable, myDimensionServiceKey,
myResizable, myTitle, myCallback, myCancelOnClickOutside, myListeners, myUseDimServiceForXYLocation, myCommandButton,
myCancelButton, myCancelOnMouseOutCallback, myCancelOnWindow, myTitleIcon, myCancelKeyEnabled, myLocateByContent,
myPlaceWithinScreen, myMinSize, myAlpha, myMaskProvider, myInStack, myModalContext, myFocusOwners, myAd, myAdAlignment,
false, myKeyboardActions, mySettingsButtons, myPinCallback, myMayBeParent,
myShowShadow, myShowBorder, myBorderColor, myCancelOnWindowDeactivation, myKeyEventHandler
);
popup.setNormalWindowLevel(myNormalWindowLevel);
popup.setOkHandler(myOkHandler);
if (myUserData != null) {
popup.setUserData(myUserData);
}
Disposer.register(ApplicationManager.getApplication(), popup);
return popup;
}
很明显,init
方法是核心:
@NotNull
protected AbstractPopup init(Project project,
@NotNull JComponent component,
@Nullable JComponent preferredFocusedComponent,
boolean requestFocus,
boolean focusable,
boolean movable,
String dimensionServiceKey,
boolean resizable,
@Nullable String caption,
@Nullable Computable<Boolean> callback,
boolean cancelOnClickOutside,
@Nullable Set<JBPopupListener> listeners,
boolean useDimServiceForXYLocation,
ActiveComponent commandButton,
@Nullable IconButton cancelButton,
@Nullable MouseChecker cancelOnMouseOutCallback,
boolean cancelOnWindow,
@Nullable ActiveIcon titleIcon,
boolean cancelKeyEnabled,
boolean locateByContent,
boolean placeWithinScreenBounds,
@Nullable Dimension minSize,
float alpha,
@Nullable MaskProvider maskProvider,
boolean inStack,
boolean modalContext,
@Nullable Component[] focusOwners,
@Nullable String adText,
int adTextAlignment,
boolean headerAlwaysFocusable,
@NotNull List<? extends Pair<ActionListener, KeyStroke>> keyboardActions,
Component settingsButtons,
@Nullable final Processor<? super JBPopup> pinCallback,
boolean mayBeParent,
boolean showShadow,
boolean showBorder,
Color borderColor,
boolean cancelOnWindowDeactivation,
@Nullable BooleanFunction<? super KeyEvent> keyEventHandler) {
// 略
myPreferredFocusedComponent = preferredFocusedComponent;
myRequestFocus = requestFocus;
// 略
可以看到我们关心的入参preferredFocusedComponent
被赋值给了myPreferredFocusedComponent
,查看引用:
仔细观察,有个很明显的获取焦点语句,感觉离真相越来越近了,搞他:
if (myRequestFocus) {
if (myPreferredFocusedComponent != null) {
myPreferredFocusedComponent.requestFocus();
}
看到了myRequestFocus
这个变量,也就是说,myRequestFocus
为true
的时候我们传入的textField才能获取到焦点。那么,myRequestFocus
变量从何而来,如果足够细心,就会发现是在init
方法传入的,而init
是在createPopup
中调用的,再次查看createPopup
方法:
到了这里,解决方案就很明显了,修改示例代码如下:
popup.setRequestFocus(true);
运行结果:
得到焦点后效果
完美解决
但凡官方文档齐全一点,也不至于学得这么辛苦
3、ConfirmPopupAction
确认取消式弹出菜单
ConfirmPopupAction
package com.kungyu.popup;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.popup.*;
import org.jetbrains.annotations.NotNull;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/**
* @author wengyongcheng
* @since 2020/3/3 10:48 下午
*/
public class ConfirmPopupAction extends AnAction implements ListSelectionListener, JBPopupListener {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
// 确认类型popup,展示俩个选项,对应执行俩种操作,TODO 这里不知道为什么选中其中一个之后必定会先弹出no信息框
ListPopup confirmation = JBPopupFactory.getInstance().createConfirmation("confirm", "yes", "no", () -> Messages.showMessageDialog("yes", "yes", Messages.getInformationIcon()), () -> Messages.showMessageDialog("no", "no", Messages.getInformationIcon()), 0);
confirmation.showInBestPositionFor(e.getDataContext());
}
/**
* 上下键选择事件,仅仅是改变,而不是按住回车之后的选择
* @param e
*/
@Override
public void valueChanged(ListSelectionEvent e) {
int firstIndex = e.getFirstIndex();
int lastIndex = e.getLastIndex();
boolean valueIsAdjusting = e.getValueIsAdjusting();
System.out.println("firstIndex:" + firstIndex);
System.out.println("lastIndex:" + lastIndex);
System.out.println("valueIsAdjusting:" + valueIsAdjusting);
}
/**
* popup 关闭监听
* @param event
*/
@Override
public void onClosed(@NotNull LightweightWindowEvent event) {
JBPopup jbPopup = event.asPopup();
System.out.println("关闭popup");
jbPopup.cancel();
}
}
注册Action
<action id="com.kungyu.popup.ConfirmPopupAction" class="com.kungyu.popup.ConfirmPopupAction"
text="ConfirmPopupAction" description="ConfirmPopupAction">
<add-to-group group-id="PopupMenuActions" anchor="first"/>
<keyboard-shortcut first-keystroke="control alt 1" keymap="Mac OS X 10.5+"/>
</action>
ConfirmPopupAction
4、MultiChoosePopupAction
多选式弹出菜单
MultiChoosePopupAction
package com.kungyu.popup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.ui.popup.*;
import org.jetbrains.annotations.NotNull;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.text.Collator;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author wengyongcheng
* @since 2020/3/3 10:48 下午
*/
public class MultiChoosePopupAction extends AnAction implements ListSelectionListener, JBPopupListener {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
// 可以多选,如果传入的list是action,选中不会触发对应的actionPerformed方法
IPopupChooserBuilder<String> popupChooserBuilder = JBPopupFactory.getInstance().createPopupChooserBuilder(Stream.of("value1","value2").collect(Collectors.toList()));
JBPopup popup = popupChooserBuilder.createPopup();
popup.showInBestPositionFor(e.getDataContext());
}
/**
* 上下键选择事件,仅仅是改变,而不是按住回车之后的选择
* @param e
*/
@Override
public void valueChanged(ListSelectionEvent e) {
int firstIndex = e.getFirstIndex();
int lastIndex = e.getLastIndex();
boolean valueIsAdjusting = e.getValueIsAdjusting();
System.out.println("firstIndex:" + firstIndex);
System.out.println("lastIndex:" + lastIndex);
System.out.println("valueIsAdjusting:" + valueIsAdjusting);
}
/**
* popup 关闭监听
* @param event
*/
@Override
public void onClosed(@NotNull LightweightWindowEvent event) {
JBPopup jbPopup = event.asPopup();
System.out.println("关闭popup");
jbPopup.cancel();
}
}
注册Action
<action id="com.kungyu.popup.MultiChoosePopupAction" class="com.kungyu.popup.MultiChoosePopupAction"
text="MultiChoosePopupAction" description="MultiChoosePopupAction">
<add-to-group group-id="PopupMenuActions" anchor="first"/>
<keyboard-shortcut first-keystroke="control alt 1" keymap="Mac OS X 10.5+"/>
</action>
效果
MultiChoosePopupAction5、SubGroupPopupAction
子菜单式弹出菜单
SubGroupPopupAction
package com.kungyu.popup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.ui.popup.*;
import org.jetbrains.annotations.NotNull;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/**
* @author wengyongcheng
* @since 2020/3/3 10:48 下午
*/
public class SubGroupPopupAction extends AnAction implements ListSelectionListener, JBPopupListener {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
// 嵌套action
DefaultActionGroup actionGroup = (DefaultActionGroup) ActionManager.getInstance().getAction("subOuterGroup");
ListPopup listPopup = JBPopupFactory.getInstance().createActionGroupPopup("SubOuterGroup",actionGroup,e.getDataContext(), JBPopupFactory.ActionSelectionAid.SPEEDSEARCH,false);
listPopup.showInFocusCenter();
listPopup.addListSelectionListener(this);
listPopup.addListener(this);
}
/**
* 上下键选择事件,仅仅是改变,而不是按住回车之后的选择
* @param e
*/
@Override
public void valueChanged(ListSelectionEvent e) {
int firstIndex = e.getFirstIndex();
int lastIndex = e.getLastIndex();
boolean valueIsAdjusting = e.getValueIsAdjusting();
System.out.println("firstIndex:" + firstIndex);
System.out.println("lastIndex:" + lastIndex);
System.out.println("valueIsAdjusting:" + valueIsAdjusting);
}
/**
* popup 关闭监听
* @param event
*/
@Override
public void onClosed(@NotNull LightweightWindowEvent event) {
JBPopup jbPopup = event.asPopup();
System.out.println("关闭popup");
jbPopup.cancel();
}
}
注册Action
<action id="com.kungyu.popup.SubGroupPopupAction" class="com.kungyu.popup.SubGroupPopupAction"
text="SubGroupPopupAction" description="SubGroupPopupAction">
<add-to-group group-id="PopupMenuActions" anchor="first"/>
<keyboard-shortcut first-keystroke="control alt 1" keymap="Mac OS X 10.5+"/>
</action>
<group id="subOuterGroup"/>
<!-- popup为true代表这是一个子菜单 -->
<group id="subInnerGroup" popup="true" text="SubInnerGroup">
<add-to-group group-id="subOuterGroup" anchor="first"/>
<action class="com.kungyu.popup.SubAction" id="subAction"
text="SubAction" description="SubAction">
</action>
</group>
SubAction
package com.kungyu.popup;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.ui.Messages;
import org.jetbrains.annotations.NotNull;
/**
* @author wengyongcheng
* @since 2020/3/4 5:28 下午
*/
public class SubAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Messages.showMessageDialog("nest", "nest", Messages.getInformationIcon());
}
}
SubGroupPopupAction
网友评论