一緒に書く習慣をつけましょう!「ナゲッツデイリーニュープラン・4月アップデートチャレンジ」に参加して7日目です。クリックしてイベントの詳細をご覧ください。
序文
私があなたに尋ねた場合、プログラムを開始するためにダブルクリックする以外に、デスクトップアイコンは他に何ができますか?
もちろん、ゲームをプレイすると、下の写真が表示されます。
ダウンロードアドレス: houxinlin.com:8080/ Game.rar
このアドレスのサーバーは、前の章のアセンブリを使用して実装されていることに注意してください。実際に簡単にサポートできるかどうかを確認したいと思います。アクセスできない場合は、メッセージを残してください。
これがその方法です。
実装の原則
この記事では、ほとんどのことを知る必要があります。公式を参照してください。詳細を知りたい場合は、最初にWindows開発を理解する必要があります。この記事では、各知識ポイントについて詳しく説明しません。
Javaが実装できない問題が2つあります。つまり、ホットキーの登録とデスクトップアイコンの移動です。したがって、この部分はcで実装され、以下は定義されたjniインターフェイスの一部です。
public class GameJNI {
public native void registerHotkeyCallback(HotkeyCallback callback);
public native int getDesktopIcon();
public native void moveDesktopIcon(int index,int x,int y);
}
复制代码
Windowsのメカニズムを理解していないと、デスクトップアイコンの移動方法を理解できない可能性があります。つまり、Windowsはメッセージメカニズムに基づいています。メッセージはウィンドウに何が起こったかを伝えます。たとえば、登録するとホットキー、ホットキーはPress itと呼ばれ、Windowsのウィンドウ関数で応答を受け取ります。ウィンドウ関数は特定のメッセージの識別子を取得します。ホットキーの識別子はWM_HOTKEYであり、他のパラメータが渡されます。 wParam、lParamなどのシステムを一緒に。
一方、アイコンを移動すると、アイコンが配置されているウィンドウにメッセージが送信され、誰かがアイコンの位置を変更したいことが示されます。
まず、registerHotkeyCallbackの実装を見てください。
void RegisterHotkey(int id,int vkCode) {
if (RegisterHotKey(NULL, id, 0, vkCode) == 0) {
printf("fail %d\n", vkCode);
}
}
JNIEXPORT void JNICALL Java_com_h_game_jni_GameJNI_registerHotkeyCallback
(JNIEnv *env, jobject thisObj, jobject callbackObject) {
RegisterHotkey(LEFT_ID,37);
RegisterHotkey(UP_ID,38);
RegisterHotkey(RIGHT_ID,39);
RegisterHotkey(DOWN_ID,40);
fflush(stdout);
MSG lpMsg = {0};
while (GetMessage(&lpMsg, NULL, 0, 0)!=0){
jclass thisClass = (*env)->GetObjectClass(env, callbackObject);
jmethodID hotKeyCallbackMethodId = (*env)->GetMethodID(env, thisClass, "hotkey", "(I)V");
if (NULL == hotKeyCallbackMethodId) return;
if (lpMsg.message== WM_HOTKEY){
jstring result = (jstring)(*env)->CallObjectMethod(env, callbackObject, hotKeyCallbackMethodId, lpMsg.wParam);
}
}
}
复制代码
コードのこの部分が最も多いです。まず、RegisterHotKey関数を使用して4つのホットキー(←↑→↓)を登録し、それらに1、2、3、および4のIDを指定してから、メッセージキューからメッセージを継続的に取得します。 GetMessage、GetMessageは通常、フォームアプリケーションで使用されますが、現時点ではフォームがないため、2番目のパラメーターはNULLで、最初のパラメーターはキューに到着するメッセージ情報です。メッセージがWM_HOTKEYの場合、定義されたホットキーを押し、javaが提供するインタラクティブAPIを介してコールバックアドレスを取得し、呼び出しを行います。
デスクトップアイコンの数を取得するために使用されるgetDesktopIconの実装を見ると、デスクトップはウィンドウとしても使用でき、デスクトップ上のアイコンのリストはListViewです。ListViewがLVM_GETITEMCOUNTメッセージを受信すると、誰かが自分のアイコンの数を取得したい場合、彼は私たちに戻ります。Windowsは、指定されたウィンドウにメッセージを送信するために使用され、戻り値を持つSendMessage関数と、戻り値はありません。これについては以下で説明します。
int GetDesktopIconCount() {
return SendMessage(GetWindowHwnd(), LVM_GETITEMCOUNT, 0, 0);
}
JNIEXPORT jint JNICALL Java_com_h_game_jni_GameJNI_getDesktopIcon
(JNIEnv *env, jobject thisObj) {
return GetDesktopIconCount();
}
复制代码
デスクトップでListViewのハンドルを取得するには、これを行う必要がありますが、各システムのデスクトップ構造が異なる可能性があり、Win7とWin10が異なるため、このコードがWin11で正常に実行できるかどうかはわかりません。 Win11の場合、この構造を変更しなくても、このコードは正常に実行されます。
int GetWindowHwnd() {
HWND hwndWorkerW = { 0 };
hwndWorkerW = FindWindow(NULL,TEXT("Program Manager"));
hwndWorkerW = FindWindowEx(hwndWorkerW, 0, TEXT("SHELLDLL_DefView"), NULL);
hwndWorkerW = FindWindowEx(hwndWorkerW, 0,TEXT("SysListView32"), TEXT("FolderView"));
return hwndWorkerW;
}
复制代码
ここには、ハンドルと検索ハンドルという多くの知識ポイントがあります。ハンドルは整数であり、一意のアプリケーションウィンドウを識別するために使用されます。検索ハンドルは、クラス名またはウィンドウタイトル名に従って検索できます。これにより、子と親の関係を含めます。詳細については、CreateWindow関数を理解する必要があります。
モバイルアイコンの実装は次のとおりです。ListViewがLVM_SETITEMPOSITIONメッセージを受信すると、他のパラメータに従って位置を設定します。ここでは、戻り値が必要ないため、PostMessageを使用してメッセージを配信します。SendMessageを使用する場合、それははるかに遅くなります。
void MoveDesktopIcon(int iconIndex,int x,int y) {
PostMessage(GetWindowHwnd(), LVM_SETITEMPOSITION, iconIndex, ConversionXY(x, y));
}
int ConversionXY(int x, int y) {
return y * 65536 + x;
}
JNIEXPORT void JNICALL Java_com_h_game_jni_GameJNI_moveDesktopIcon
(JNIEnv *env, jobject thisObj, jint index, jint x, jint y) {
MoveDesktopIcon(index, x, y);
}
复制代码
さて、以下はJavaコードの実装です。
public class Main {
static {
System.load("Game.dll");
}
public static void main(String[] args) {
new SnakeGame();
}
}
复制代码
この部分は比較的単純で、言うまでもなく、アイコンの基本的な操作はcによって提供されており、ここから直接呼び出すことができます。
ここでは、アイコンインデックス0を頭、最後のインデックスを食べ物として使用していることに注意してください。たとえば、10個のアイコンがあり、0は頭、9は食べ物、1〜8は体です。方向に応じて頭を動かし、最初に位置の前の動きを記録し、頭が動いたら、記録した位置を最初のアイコンに渡し、次にインデックス1の元の位置を2番目のアイコンに渡し、以下同様に貪欲なヘビを形成します。
public class SnakeGame extends JFrame implements HotkeyCallback {
private static final int WINDOW_WIDTH = 300;
private static final int WINDOW_HEIGHT = 200;
private GameJNI gameJNI = new GameJNI();
private ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);
private Direction direction = Direction.RIGHT;
private Point snakePoint = new Point(0, 0);
private static final int MOVE_SIZE = 84;
private List<Point> snakeBodyPoint = new ArrayList<>();
private Point foodPoint = new Point(0, 0);
private List<Point> allPoint = generatorPoints();
private ScheduledFuture<?> scheduledFuture = null;
public SnakeGame() {
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
this.setLocation(screenSize.width / 2 - WINDOW_WIDTH / 2, screenSize.height / 2 - WINDOW_HEIGHT / 2);
this.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
init();
this.setVisible(true);
}
private void startGame() {
this.setVisible(false);
if (scheduledFuture == null) {
scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(() -> {
Point oldPoint = snakePoint.getLocation();
if (direction == Direction.LEFT) snakePoint.x -= MOVE_SIZE;
if (direction == Direction.UP) snakePoint.y -= MOVE_SIZE;
if (direction == Direction.RIGHT) snakePoint.x += MOVE_SIZE;
if (direction == Direction.DOWN) snakePoint.y += MOVE_SIZE;
moveSnakeHeader();
moveSnakeBody(oldPoint);
isCollision();
}, 0, 200, TimeUnit.MILLISECONDS);
}
}
private void isCollision() {
if (snakeBodyPoint.stream().anyMatch(point -> point.equals(snakePoint))) {
scheduledFuture.cancel(true);
scheduledFuture = null;
this.setVisible(true);
}
}
private void moveSnakeHeader() {
gameJNI.moveDesktopIcon(0, snakePoint.x, snakePoint.y);
if (foodPoint.equals(snakePoint)) resetBodyLocation();
}
private List<Point> generatorPoints() {
List<Point> all = new ArrayList<>();
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
for (int i = 0; i < screenSize.width; i += MOVE_SIZE) {
for (int j = 0; j < screenSize.height; j += MOVE_SIZE) {
all.add(new Point(i, j));
}
}
return all;
}
private void resetBodyLocation() {
List<Point> newPoint = allPoint.stream().filter(point -> !has(point)).collect(Collectors.toList());
Collections.shuffle(newPoint);
Point point = newPoint.get(0);
int desktopIcon = gameJNI.getDesktopIcon();
foodPoint.setLocation(point.x, point.y);
gameJNI.moveDesktopIcon(desktopIcon - 1, point.x, point.y);
}
private boolean has(Point hasPoint) {
return snakeBodyPoint.stream().anyMatch(point -> hasPoint.equals(point));
}
private void moveSnakeBody(Point oldPoint) {
for (int i = 1; i < snakeBodyPoint.size() - 1; i++) {
Point itemPoint = snakeBodyPoint.get(i);
gameJNI.moveDesktopIcon(i, oldPoint.x, oldPoint.y);
snakeBodyPoint.set(i, oldPoint.getLocation());
oldPoint = itemPoint;
}
}
private void init() {
this.setLayout(new BorderLayout());
String str ="<html>首先右击桌面,查看>取消自动排列图片、将网格与图片对齐。方向键为← ↑ → ↓</html>";
JLabel jLabel = new JLabel(str);
jLabel.setFont(new Font("黑体",0,18));
add(jLabel, BorderLayout.NORTH);
add(createButton(), BorderLayout.SOUTH);
registerHotkey();
reset();
}
private void reset() {
snakeBodyPoint.clear();
direction = Direction.RIGHT;
snakePoint.setLocation(0, 0);
int desktopIcon = gameJNI.getDesktopIcon();
int offsetX = -MOVE_SIZE;
for (int i = 0; i < desktopIcon; i++) {
snakeBodyPoint.add(new Point(offsetX, 0));
gameJNI.moveDesktopIcon(i, offsetX, 0);
offsetX -= MOVE_SIZE;
}
resetBodyLocation();
}
private JButton createButton() {
JButton jButton = new JButton("开始");
jButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
reset();
startGame();
}
});
return jButton;
}
@Override
public void hotkey(int key) {
if (key == 1) direction = Direction.LEFT;
if (key == 2) direction = Direction.UP;
if (key == 3) direction = Direction.RIGHT;
if (key == 4) direction = Direction.DOWN;
}
public void registerHotkey() {
new Thread(() -> gameJNI.registerHotkeyCallback(this)).start();
}
enum Direction {
LEFT, UP, RIGHT, DOWN
}
}
复制代码