mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
# Conflicts:
# docs/src/content/docs/changelog.mdx # v3/pkg/application/menuitem.go
This commit is contained in:
parent
f01b4b9a21
commit
be8159d410
19 changed files with 889 additions and 56 deletions
|
|
@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Add window to context when calling a service method by [@leaanthony](https://github.com/leaanthony)
|
||||
- Add `window-call` example to demonstrate how to know which window is calling a service by [@leaanthony](https://github.com/leaanthony)
|
||||
- Better panic handling by [@leaanthony](https://github.com/leaanthony)
|
||||
- New Menu guide by [@leaanthony](https://github.com/leaanthony)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
@ -50,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- Removed `application.WindowIDKey` and `application.WindowNameKey` (replaced by `application.WindowKey`) by [@leaanthony](https://github.com/leaanthony)
|
||||
- In JS/TS bindings, class fields of fixed-length array types are now initialized with their expected length instead of being empty by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001)
|
||||
- ContextMenuData now returns a string instead of any by [@leaanthony](https://github.com/leaanthony)
|
||||
|
||||
## v3.0.0-alpha.9 - 2025-01-13
|
||||
|
||||
|
|
|
|||
491
docs/src/content/docs/guides/menus.mdx
Normal file
491
docs/src/content/docs/guides/menus.mdx
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
---
|
||||
title: Menus
|
||||
description: A guide to creating and customising menus in Wails v3
|
||||
---
|
||||
|
||||
Wails v3 provides a powerful menu system that allows you to create both application menus and context menus. This guide will walk you through the various features and capabilities of the menu system.
|
||||
|
||||
### Creating a Menu
|
||||
|
||||
To create a new menu, use the `NewMenu()` method from your application instance:
|
||||
|
||||
```go
|
||||
menu := application.NewMenu()
|
||||
```
|
||||
|
||||
### Adding Menu Items
|
||||
|
||||
Wails supports several types of menu items, each serving a specific purpose:
|
||||
|
||||
#### Regular Menu Items
|
||||
Regular menu items are the basic building blocks of menus. They display text and can trigger actions when clicked:
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Click Me")
|
||||
```
|
||||
|
||||
#### Checkboxes
|
||||
Checkbox menu items provide a toggleable state, useful for enabling/disabling features or settings:
|
||||
|
||||
```go
|
||||
checkbox := menu.AddCheckbox("My checkbox", true) // true = initially checked
|
||||
```
|
||||
|
||||
#### Radio Groups
|
||||
Radio groups allow users to select one option from a set of mutually exclusive choices. They are automatically created when radio items are placed next to each other:
|
||||
|
||||
```go
|
||||
menu.AddRadio("Option 1", true) // true = initially selected
|
||||
menu.AddRadio("Option 2", false)
|
||||
menu.AddRadio("Option 3", false)
|
||||
```
|
||||
|
||||
#### Separators
|
||||
Separators are horizontal lines that help organise menu items into logical groups:
|
||||
|
||||
```go
|
||||
menu.AddSeparator()
|
||||
```
|
||||
|
||||
#### Submenus
|
||||
Submenus are nested menus that appear when hovering over or clicking a menu item. They're useful for organizing complex menu structures:
|
||||
|
||||
```go
|
||||
submenu := menu.AddSubmenu("File")
|
||||
submenu.Add("Open")
|
||||
submenu.Add("Save")
|
||||
```
|
||||
|
||||
### Menu Item Properties
|
||||
|
||||
Menu items have several properties that can be configured:
|
||||
|
||||
| Property | Method | Description |
|
||||
|-------------|--------------------------|-----------------------------------------------------|
|
||||
| Label | `SetLabel(string)` | Sets the display text |
|
||||
| Enabled | `SetEnabled(bool)` | Enables/disables the item |
|
||||
| Checked | `SetChecked(bool)` | Sets the checked state (for checkboxes/radio items) |
|
||||
| Tooltip | `SetTooltip(string)` | Sets the tooltip text |
|
||||
| Hidden | `SetHidden(bool)` | Shows/hides the item |
|
||||
| Accelerator | `SetAccelerator(string)` | Sets the keyboard shortcut |
|
||||
|
||||
### Menu Item States
|
||||
|
||||
Menu items can be in different states that control their visibility and interactivity:
|
||||
|
||||
#### Visibility
|
||||
|
||||
Menu items can be shown or hidden dynamically using the `SetHidden()` method:
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Dynamic Item")
|
||||
|
||||
// Hide the menu item
|
||||
menuItem.SetHidden(true)
|
||||
|
||||
// Show the menu item
|
||||
menuItem.SetHidden(false)
|
||||
|
||||
// Check current visibility
|
||||
isHidden := menuItem.Hidden()
|
||||
```
|
||||
|
||||
Hidden menu items are completely removed from the menu until shown again. This is useful for contextual menu items that should only appear in certain application states.
|
||||
|
||||
#### Enabled State
|
||||
|
||||
Menu items can be enabled or disabled using the `SetEnabled()` method:
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Save")
|
||||
|
||||
// Disable the menu item
|
||||
menuItem.SetEnabled(false) // Item appears grayed out and cannot be clicked
|
||||
|
||||
// Enable the menu item
|
||||
menuItem.SetEnabled(true) // Item becomes clickable again
|
||||
|
||||
// Check current enabled state
|
||||
isEnabled := menuItem.Enabled()
|
||||
```
|
||||
|
||||
Disabled menu items remain visible but appear grayed out and cannot be clicked. This is commonly used to indicate that an action is currently unavailable, such as:
|
||||
- Disabling "Save" when there are no changes to save
|
||||
- Disabling "Copy" when nothing is selected
|
||||
- Disabling "Undo" when there's no action to undo
|
||||
|
||||
#### Dynamic State Management
|
||||
|
||||
You can combine these states with event handlers to create dynamic menus:
|
||||
|
||||
```go
|
||||
saveMenuItem := menu.Add("Save")
|
||||
|
||||
// Initially disable the Save menu item
|
||||
saveMenuItem.SetEnabled(false)
|
||||
|
||||
// Enable Save only when there are unsaved changes
|
||||
documentChanged := func() {
|
||||
saveMenuItem.SetEnabled(true)
|
||||
menu.Update() // Remember to update the menu after changing states
|
||||
}
|
||||
|
||||
// Disable Save after saving
|
||||
documentSaved := func() {
|
||||
saveMenuItem.SetEnabled(false)
|
||||
menu.Update()
|
||||
}
|
||||
```
|
||||
|
||||
### Event Handling
|
||||
|
||||
Menu items can respond to click events using the `OnClick` method:
|
||||
|
||||
```go
|
||||
menuItem.OnClick(func(ctx *application.Context) {
|
||||
// Handle the click event
|
||||
println("Menu item clicked!")
|
||||
})
|
||||
```
|
||||
|
||||
The context provides information about the clicked menu item:
|
||||
|
||||
```go
|
||||
menuItem.OnClick(func(ctx *application.Context) {
|
||||
// Get the clicked menu item
|
||||
clickedItem := ctx.ClickedMenuItem()
|
||||
// Get its current state
|
||||
isChecked := clickedItem.Checked()
|
||||
})
|
||||
```
|
||||
|
||||
### Role-Based Menu Items
|
||||
|
||||
Wails provides a set of predefined menu roles that automatically create menu items with standard functionality. Here are the supported menu roles:
|
||||
|
||||
#### Complete Menu Structures
|
||||
These roles create entire menu structures with common functionality:
|
||||
|
||||
| Role | Description | Platform Notes |
|
||||
|------|-------------|----------------|
|
||||
| `AppMenu` | Application menu with About, Services, Hide/Show, and Quit | macOS only |
|
||||
| `EditMenu` | Standard Edit menu with Undo, Redo, Cut, Copy, Paste, etc. | All platforms |
|
||||
| `ViewMenu` | View menu with Reload, Zoom, and Fullscreen controls | All platforms |
|
||||
| `WindowMenu` | Window controls (Minimise, Zoom, etc.) | All platforms |
|
||||
| `HelpMenu` | Help menu with "Learn More" link to Wails website | All platforms |
|
||||
|
||||
#### Individual Menu Items
|
||||
These roles can be used to add individual menu items:
|
||||
|
||||
| Role | Description | Platform Notes |
|
||||
|------|-------------|----------------|
|
||||
| `About` | Show application About dialog | All platforms |
|
||||
| `Hide` | Hide application | macOS only |
|
||||
| `HideOthers` | Hide other applications | macOS only |
|
||||
| `UnHide` | Show hidden application | macOS only |
|
||||
| `CloseWindow` | Close current window | All platforms |
|
||||
| `Minimise` | Minimise window | All platforms |
|
||||
| `Zoom` | Zoom window | macOS only |
|
||||
| `Front` | Bring window to front | macOS only |
|
||||
| `Quit` | Quit application | All platforms |
|
||||
| `Undo` | Undo last action | All platforms |
|
||||
| `Redo` | Redo last action | All platforms |
|
||||
| `Cut` | Cut selection | All platforms |
|
||||
| `Copy` | Copy selection | All platforms |
|
||||
| `Paste` | Paste from clipboard | All platforms |
|
||||
| `PasteAndMatchStyle` | Paste and match style | macOS only |
|
||||
| `SelectAll` | Select all | All platforms |
|
||||
| `Delete` | Delete selection | All platforms |
|
||||
| `Reload` | Reload current page | All platforms |
|
||||
| `ForceReload` | Force reload current page | All platforms |
|
||||
| `ToggleFullscreen` | Toggle fullscreen mode | All platforms |
|
||||
| `ResetZoom` | Reset zoom level | All platforms |
|
||||
| `ZoomIn` | Increase zoom | All platforms |
|
||||
| `ZoomOut` | Decrease zoom | All platforms |
|
||||
|
||||
Here's an example showing how to use both complete menus and individual roles:
|
||||
|
||||
```go
|
||||
menu := application.NewMenu()
|
||||
|
||||
// Add complete menu structures
|
||||
menu.AddRole(application.AppMenu) // macOS only
|
||||
menu.AddRole(application.EditMenu) // Common edit operations
|
||||
menu.AddRole(application.ViewMenu) // View controls
|
||||
menu.AddRole(application.WindowMenu) // Window controls
|
||||
|
||||
// Add individual role-based items to a custom menu
|
||||
fileMenu := menu.AddSubmenu("File")
|
||||
fileMenu.AddRole(application.CloseWindow)
|
||||
fileMenu.AddSeparator()
|
||||
fileMenu.AddRole(application.Quit)
|
||||
```
|
||||
|
||||
## Application Menus
|
||||
|
||||
Application menus are the menus that appear at the top of your application window (Windows/Linux) or at the top of the screen (macOS).
|
||||
|
||||
|
||||
### Application Menu Behaviour
|
||||
|
||||
When you set an application menu using `app.SetMenu()`, it becomes the default menu for all windows in your application. However, there are a few important behaviours to note:
|
||||
|
||||
1. **Global Application Menu**: The menu set via `app.SetMenu()` acts as the default menu for all windows.
|
||||
|
||||
2. **Per-Window Menu Override**: Individual windows can override the application menu by setting their own menu through window options:
|
||||
```go
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Custom Menu Window",
|
||||
Windows: application.WindowsWindow{
|
||||
Menu: customMenu, // Override application menu for this window
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
3. **Disable Window Menu**: On Windows, you can disable a window's menu completely even when there's a global application menu:
|
||||
```go
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "No Menu Window",
|
||||
Windows: application.WindowsWindow{
|
||||
DisableMenu: true, // Disable menu for this window
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Here's a complete example showing these different menu behaviours:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
app := application.New(application.Options{})
|
||||
|
||||
// Create application menu
|
||||
appMenu := application.NewMenu()
|
||||
fileMenu := appMenu.AddSubmenu("File")
|
||||
fileMenu.Add("New").OnClick(func(ctx *application.Context) {
|
||||
// This will be available in all windows unless overridden
|
||||
window := app.CurrentWindow()
|
||||
window.SetTitle("New Window")
|
||||
})
|
||||
|
||||
// Set as application menu - default for all windows
|
||||
app.SetMenu(appMenu)
|
||||
|
||||
// Window with default application menu
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Default Menu",
|
||||
})
|
||||
|
||||
// Window with custom menu
|
||||
customMenu := application.NewMenu()
|
||||
customMenu.Add("Custom Action")
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Custom Menu",
|
||||
Windows: application.WindowsWindow{
|
||||
Menu: customMenu,
|
||||
},
|
||||
})
|
||||
|
||||
// Window with no menu
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "No Menu",
|
||||
Windows: application.WindowsWindow{
|
||||
DisableMenu: true,
|
||||
},
|
||||
})
|
||||
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
## Context Menus
|
||||
|
||||
Context menus are popup menus that appear when right-clicking elements in your application. They provide quick access to relevant actions for the clicked element.
|
||||
|
||||
### Default Context Menu
|
||||
|
||||
The default context menu is the webview's built-in context menu that provides system-level operations such as:
|
||||
- Copy, Cut, and Paste for text manipulation
|
||||
- Text selection controls
|
||||
- Spell checking options
|
||||
|
||||
#### Controlling the Default Context Menu
|
||||
|
||||
You can control when the default context menu appears using the `--default-contextmenu` CSS property:
|
||||
|
||||
```html
|
||||
<!-- Always show default context menu -->
|
||||
<div style="--default-contextmenu: show">
|
||||
<input type="text" placeholder="Right-click for text operations"/>
|
||||
<textarea>Standard text operations available here</textarea>
|
||||
</div>
|
||||
|
||||
<!-- Hide default context menu -->
|
||||
<div style="--default-contextmenu: hide">
|
||||
<div class="custom-component">Custom context menu only</div>
|
||||
</div>
|
||||
|
||||
<!-- Smart context menu behaviour (default) -->
|
||||
<div style="--default-contextmenu: auto">
|
||||
<!-- Shows default menu when text is selected or in input fields -->
|
||||
<p>Select this text to see the default menu</p>
|
||||
<input type="text" placeholder="Default menu for input operations"/>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Nested Context Menu Behavior
|
||||
|
||||
When using the `--default-contextmenu` property on nested elements, the following rules apply:
|
||||
|
||||
1. Child elements inherit their parent's context menu setting unless explicitly overridden
|
||||
2. The most specific (closest) setting takes precedence
|
||||
3. The `auto` value can be used to reset to default behaviour
|
||||
|
||||
Example of nested context menu behaviour:
|
||||
|
||||
```html
|
||||
<!-- Parent sets hide -->
|
||||
<div style="--default-contextmenu: hide">
|
||||
<!-- This inherits hide -->
|
||||
<p>No context menu here</p>
|
||||
|
||||
<!-- This overrides to show -->
|
||||
<div style="--default-contextmenu: show">
|
||||
<p>Context menu shown here</p>
|
||||
|
||||
<!-- This inherits show -->
|
||||
<span>Also has context menu</span>
|
||||
|
||||
<!-- This resets to automatic behaviour -->
|
||||
<div style="--default-contextmenu: auto">
|
||||
<p>Shows menu only when text is selected</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Custom Context Menus
|
||||
|
||||
Custom context menus allow you to provide application-specific actions that are relevant to the element being clicked. They're particularly useful for:
|
||||
- File operations in a document manager
|
||||
- Image manipulation tools
|
||||
- Custom actions in a data grid
|
||||
- Component-specific operations
|
||||
|
||||
#### Creating a Custom Context Menu
|
||||
|
||||
When creating a custom context menu, you provide a unique identifier (name) that links the menu to HTML elements:
|
||||
|
||||
```go
|
||||
// Create a context menu with identifier "imageMenu"
|
||||
contextMenu := application.NewContextMenu("imageMenu")
|
||||
```
|
||||
|
||||
The name parameter ("imageMenu" in this example) serves as a unique identifier that will be used to:
|
||||
1. Link HTML elements to this specific context menu
|
||||
2. Identify which menu should be shown when right-clicking
|
||||
3. Allow menu updates and cleanup
|
||||
|
||||
#### Context Data
|
||||
|
||||
When handling context menu events, you can access both the clicked menu item and its associated context data:
|
||||
|
||||
```go
|
||||
contextMenu.Add("Process").OnClick(func(ctx *application.Context) {
|
||||
// Get the clicked menu item
|
||||
menuItem := ctx.ClickedMenuItem()
|
||||
|
||||
// Get the context data as a string
|
||||
contextData := ctx.ContextMenuData()
|
||||
|
||||
// Check if the menu item is checked (for checkbox/radio items)
|
||||
isChecked := ctx.IsChecked()
|
||||
|
||||
// Use the data
|
||||
if contextData != "" {
|
||||
processItem(contextData)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
The context data is passed from the HTML element's `--custom-contextmenu-data` property and is available in the click handler through `ctx.ContextMenuData()`. This is particularly useful when:
|
||||
|
||||
- Working with lists or grids where each item needs unique identification
|
||||
- Handling operations on specific components or elements
|
||||
- Passing state or metadata from the frontend to the backend
|
||||
|
||||
#### Context Menu Management
|
||||
|
||||
After making changes to a context menu, call the `Update()` method to apply the changes:
|
||||
|
||||
```go
|
||||
contextMenu.Update()
|
||||
```
|
||||
|
||||
When you no longer need a context menu, you can destroy it:
|
||||
|
||||
```go
|
||||
contextMenu.Destroy()
|
||||
```
|
||||
:::danger[Warning]
|
||||
After calling `Destroy()`, using the context menu reference again will result in a panic.
|
||||
:::
|
||||
|
||||
### Real-World Example: Image Gallery
|
||||
|
||||
Here's a complete example of implementing a custom context menu for an image gallery:
|
||||
|
||||
```go
|
||||
// Backend: Create the context menu
|
||||
imageMenu := application.NewContextMenu("imageMenu")
|
||||
|
||||
// Add relevant operations
|
||||
imageMenu.Add("View Full Size").OnClick(func(ctx *application.Context) {
|
||||
// Get the image ID from context data
|
||||
if imageID := ctx.ContextMenuData(); imageID != "" {
|
||||
openFullSizeImage(imageID)
|
||||
}
|
||||
})
|
||||
|
||||
imageMenu.Add("Download").OnClick(func(ctx *application.Context) {
|
||||
if imageID := ctx.ContextMenuData(); imageID != "" {
|
||||
downloadImage(imageID)
|
||||
}
|
||||
})
|
||||
|
||||
imageMenu.Add("Share").OnClick(func(ctx *application.Context) {
|
||||
if imageID := ctx.ContextMenuData(); imageID != "" {
|
||||
showShareDialog(imageID)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- Frontend: Image gallery implementation -->
|
||||
<div class="gallery">
|
||||
<!-- Each image container with context menu -->
|
||||
<div class="image-container"
|
||||
style="--custom-contextmenu: imageMenu; --custom-contextmenu-data: img_123">
|
||||
<img src="/images/img_123.jpg" alt="Gallery Image"/>
|
||||
<span class="caption">Nature Photo</span>
|
||||
</div>
|
||||
|
||||
<div class="image-container"
|
||||
style="--custom-contextmenu: imageMenu; --custom-contextmenu-data: img_124">
|
||||
<img src="/images/img_124.jpg" alt="Gallery Image"/>
|
||||
<span class="caption">City Photo</span>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
In this example:
|
||||
1. The context menu is created with the identifier "imageMenu"
|
||||
2. Each image container is linked to the menu using `--custom-contextmenu: imageMenu`
|
||||
3. Each container provides its image ID as context data using `--custom-contextmenu-data`
|
||||
4. The backend receives the image ID in click handlers and can perform specific operations
|
||||
5. The same menu is reused for all images, but the context data tells us which image to operate on
|
||||
|
||||
This pattern is particularly powerful for:
|
||||
- Data grids where rows need specific operations
|
||||
- File managers where files need context-specific actions
|
||||
- Design tools where different elements need different operations
|
||||
- Any component where the same operations apply to multiple instances
|
||||
|
|
@ -24,7 +24,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
|
||||
mainWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Context Menu Demo",
|
||||
Width: 1024,
|
||||
Height: 1024,
|
||||
|
|
@ -35,22 +35,15 @@ func main() {
|
|||
},
|
||||
})
|
||||
|
||||
contextMenu := app.NewMenu()
|
||||
contextMenu.Add("Click Me").OnClick(func(data *application.Context) {
|
||||
contextMenu := application.NewContextMenu("test")
|
||||
clickMe := contextMenu.Add("Set Menuitem label to Context Data")
|
||||
contextDataMenuItem := contextMenu.Add("No Context Data")
|
||||
clickMe.OnClick(func(data *application.Context) {
|
||||
app.Logger.Info("Context menu", "context data", data.ContextMenuData())
|
||||
contextDataMenuItem.SetLabel("Current context data: " + data.ContextMenuData())
|
||||
contextMenu.Update()
|
||||
})
|
||||
|
||||
globalContextMenu := app.NewMenu()
|
||||
globalContextMenu.Add("Default context menu item").OnClick(func(data *application.Context) {
|
||||
app.Logger.Info("Context menu", "context data", data.ContextMenuData())
|
||||
})
|
||||
|
||||
// Registering the menu with a window will make it available to that window only
|
||||
mainWindow.RegisterContextMenu("test", contextMenu)
|
||||
|
||||
// Registering the menu with the app will make it available to all windows
|
||||
app.RegisterContextMenu("test", globalContextMenu)
|
||||
|
||||
err := app.Run()
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
24
v3/examples/window-menu/README.md
Normal file
24
v3/examples/window-menu/README.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Window Menu Example
|
||||
|
||||
*** Windows Only ***
|
||||
|
||||
This example demonstrates how to create a window with a menu bar that can be toggled using the window.ToggleMenuBar() method.
|
||||
|
||||
## Features
|
||||
|
||||
- Default menu bar with File, Edit, and Help menus
|
||||
- F1 key to toggle menu bar visibility
|
||||
- Simple HTML interface with instructions
|
||||
|
||||
## Running the Example
|
||||
|
||||
```bash
|
||||
cd v3/examples/window-menu
|
||||
go run .
|
||||
```
|
||||
|
||||
## How it Works
|
||||
|
||||
The example creates a window with a default menu and binds the F10 key to toggle the menu bar's visibility. The menu bar will hide when F10 is pressed and show when F10 is released.
|
||||
|
||||
Note: The menu bar toggling functionality only works on Windows. On other platforms, the F10 key binding will have no effect.
|
||||
14
v3/examples/window-menu/assets/about.html
Normal file
14
v3/examples/window-menu/assets/about.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Window Menu Demo</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>About Window Menu Demo</h1>
|
||||
<p>Press F1 to toggle menu bar visibility</p>
|
||||
<p>Press F2 to show menu bar</p>
|
||||
<p>Press F3 to hide menu bar</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
48
v3/examples/window-menu/assets/index.html
Normal file
48
v3/examples/window-menu/assets/index.html
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Window Menu Demo</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 2rem;
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
color: #2d2d2d;
|
||||
}
|
||||
.key {
|
||||
background: #e9e9e9;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ccc;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Window Menu Demo</h1>
|
||||
<p>This example demonstrates the menu bar visibility toggle feature.</p>
|
||||
<p>Press <span class="key">F1</span> to toggle the menu bar.</p>
|
||||
<p>Press <span class="key">F2</span> to show the menu bar.</p>
|
||||
<p>Press <span class="key">F3</span> to hide the menu bar.</p>
|
||||
<p>The menu includes:</p>
|
||||
<ul>
|
||||
<li>File menu with Exit option</li>
|
||||
<li>MenuBar menu with Hide options</li>
|
||||
<li>Help menu with About</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
26
v3/examples/window-menu/assets/style.css
Normal file
26
v3/examples/window-menu/assets/style.css
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 2rem;
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
color: #2d2d2d;
|
||||
}
|
||||
.key {
|
||||
background: #e9e9e9;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ccc;
|
||||
font-family: monospace;
|
||||
}
|
||||
64
v3/examples/window-menu/main.go
Normal file
64
v3/examples/window-menu/main.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
_ "embed"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"log"
|
||||
)
|
||||
|
||||
//go:embed assets/*
|
||||
var assets embed.FS
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Window Menu Demo",
|
||||
Description: "A demo of menu bar toggling",
|
||||
Assets: application.AssetOptions{
|
||||
Handler: application.BundledAssetFileServer(assets),
|
||||
},
|
||||
})
|
||||
|
||||
// Create a menu
|
||||
menu := app.NewMenu()
|
||||
fileMenu := menu.AddSubmenu("File")
|
||||
fileMenu.Add("Exit").OnClick(func(ctx *application.Context) {
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
editMenu := menu.AddSubmenu("MenuBar")
|
||||
editMenu.Add("Hide MenuBar").OnClick(func(ctx *application.Context) {
|
||||
app.CurrentWindow().HideMenuBar()
|
||||
})
|
||||
|
||||
helpMenu := menu.AddSubmenu("Help")
|
||||
helpMenu.Add("About").OnClick(func(ctx *application.Context) {
|
||||
app.CurrentWindow().SetURL("/about.html")
|
||||
})
|
||||
|
||||
// Create window with menu
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Window Menu Demo",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
Windows: application.WindowsWindow{
|
||||
Menu: menu,
|
||||
},
|
||||
KeyBindings: map[string]func(window *application.WebviewWindow){
|
||||
"F1": func(window *application.WebviewWindow) {
|
||||
window.ToggleMenuBar()
|
||||
},
|
||||
"F2": func(window *application.WebviewWindow) {
|
||||
window.ShowMenuBar()
|
||||
},
|
||||
"F3": func(window *application.WebviewWindow) {
|
||||
window.HideMenuBar()
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -332,7 +332,7 @@ type App struct {
|
|||
customEventProcessor *EventProcessor
|
||||
Logger *slog.Logger
|
||||
|
||||
contextMenus map[string]*Menu
|
||||
contextMenus map[string]*ContextMenu
|
||||
contextMenusLock sync.Mutex
|
||||
|
||||
assets *assetserver.AssetServer
|
||||
|
|
@ -436,7 +436,7 @@ func (a *App) init() {
|
|||
a.applicationEventListeners = make(map[uint][]*EventListener)
|
||||
a.windows = make(map[uint]Window)
|
||||
a.systemTrays = make(map[uint]*SystemTray)
|
||||
a.contextMenus = make(map[string]*Menu)
|
||||
a.contextMenus = make(map[string]*ContextMenu)
|
||||
a.keyBindings = make(map[string]func(window *WebviewWindow))
|
||||
a.Logger = a.options.Logger
|
||||
a.pid = os.Getpid()
|
||||
|
|
@ -955,13 +955,19 @@ func (a *App) Show() {
|
|||
}
|
||||
}
|
||||
|
||||
func (a *App) RegisterContextMenu(name string, menu *Menu) {
|
||||
func (a *App) registerContextMenu(menu *ContextMenu) {
|
||||
a.contextMenusLock.Lock()
|
||||
defer a.contextMenusLock.Unlock()
|
||||
a.contextMenus[name] = menu
|
||||
a.contextMenus[menu.name] = menu
|
||||
}
|
||||
|
||||
func (a *App) getContextMenu(name string) (*Menu, bool) {
|
||||
func (a *App) unregisterContextMenu(name string) {
|
||||
a.contextMenusLock.Lock()
|
||||
defer a.contextMenusLock.Unlock()
|
||||
delete(a.contextMenus, name)
|
||||
}
|
||||
|
||||
func (a *App) getContextMenu(name string) (*ContextMenu, bool) {
|
||||
a.contextMenusLock.Lock()
|
||||
defer a.contextMenusLock.Unlock()
|
||||
menu, ok := a.contextMenus[name]
|
||||
|
|
|
|||
|
|
@ -32,8 +32,17 @@ func (c *Context) IsChecked() bool {
|
|||
}
|
||||
return result.(bool)
|
||||
}
|
||||
func (c *Context) ContextMenuData() any {
|
||||
return c.data[contextMenuData]
|
||||
func (c *Context) ContextMenuData() string {
|
||||
result := c.data[contextMenuData]
|
||||
if result == nil {
|
||||
return ""
|
||||
}
|
||||
str, ok := result.(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) withClickedMenuItem(menuItem *MenuItem) *Context {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,29 @@ type menuImpl interface {
|
|||
update()
|
||||
}
|
||||
|
||||
type ContextMenu struct {
|
||||
*Menu
|
||||
name string
|
||||
}
|
||||
|
||||
func NewContextMenu(name string) *ContextMenu {
|
||||
result := &ContextMenu{
|
||||
Menu: NewMenu(),
|
||||
name: name,
|
||||
}
|
||||
result.Update()
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *ContextMenu) Update() {
|
||||
m.Menu.Update()
|
||||
globalApplication.registerContextMenu(m)
|
||||
}
|
||||
|
||||
func (m *ContextMenu) Destroy() {
|
||||
globalApplication.unregisterContextMenu(m.name)
|
||||
}
|
||||
|
||||
type Menu struct {
|
||||
items []*MenuItem
|
||||
label string
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ type ContextMenuData struct {
|
|||
Id string `json:"id"`
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
Data any `json:"data"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
func (d ContextMenuData) clone() *ContextMenuData {
|
||||
|
|
|
|||
|
|
@ -104,6 +104,9 @@ type (
|
|||
delete()
|
||||
selectAll()
|
||||
redo()
|
||||
showMenuBar()
|
||||
hideMenuBar()
|
||||
toggleMenuBar()
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -138,9 +141,6 @@ type WebviewWindow struct {
|
|||
eventHooks map[uint][]*WindowEventListener
|
||||
eventHooksLock sync.RWMutex
|
||||
|
||||
contextMenus map[string]*Menu
|
||||
contextMenusLock sync.RWMutex
|
||||
|
||||
// A map of listener cancellation functions
|
||||
cancellersLock sync.RWMutex
|
||||
cancellers []func()
|
||||
|
|
@ -247,7 +247,6 @@ func NewWindow(options WebviewWindowOptions) *WebviewWindow {
|
|||
id: thisWindowID,
|
||||
options: options,
|
||||
eventListeners: make(map[uint][]*WindowEventListener),
|
||||
contextMenus: make(map[string]*Menu),
|
||||
eventHooks: make(map[uint][]*WindowEventListener),
|
||||
menuBindings: make(map[string]*MenuItem),
|
||||
}
|
||||
|
|
@ -1176,35 +1175,21 @@ func (w *WebviewWindow) HandleDragAndDropMessage(filenames []string) {
|
|||
}
|
||||
|
||||
func (w *WebviewWindow) OpenContextMenu(data *ContextMenuData) {
|
||||
menu, ok := w.contextMenus[data.Id]
|
||||
// try application level context menu
|
||||
menu, ok := globalApplication.getContextMenu(data.Id)
|
||||
if !ok {
|
||||
// try application level context menu
|
||||
menu, ok = globalApplication.getContextMenu(data.Id)
|
||||
if !ok {
|
||||
w.Error("No context menu found for id: %s", data.Id)
|
||||
return
|
||||
}
|
||||
w.Error("No context menu found for id: %s", data.Id)
|
||||
return
|
||||
}
|
||||
menu.setContextData(data)
|
||||
if w.impl == nil || w.isDestroyed() {
|
||||
return
|
||||
}
|
||||
InvokeSync(func() {
|
||||
w.impl.openContextMenu(menu, data)
|
||||
w.impl.openContextMenu(menu.Menu, data)
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterContextMenu registers a context menu and assigns it the given name.
|
||||
func (w *WebviewWindow) RegisterContextMenu(name string, menu *Menu) {
|
||||
if menu == nil {
|
||||
w.Error("RegisterContextMenu called with nil menu")
|
||||
return
|
||||
}
|
||||
w.contextMenusLock.Lock()
|
||||
defer w.contextMenusLock.Unlock()
|
||||
w.contextMenus[name] = menu
|
||||
}
|
||||
|
||||
// NativeWindowHandle returns the platform native window handle for the window.
|
||||
func (w *WebviewWindow) NativeWindowHandle() (uintptr, error) {
|
||||
if w.impl == nil || w.isDestroyed() {
|
||||
|
|
@ -1368,3 +1353,27 @@ func (w *WebviewWindow) redo() {
|
|||
}
|
||||
w.impl.redo()
|
||||
}
|
||||
|
||||
// ShowMenuBar shows the menu bar for the window.
|
||||
func (w *WebviewWindow) ShowMenuBar() {
|
||||
if w.impl == nil || w.isDestroyed() {
|
||||
return
|
||||
}
|
||||
InvokeSync(w.impl.showMenuBar)
|
||||
}
|
||||
|
||||
// HideMenuBar hides the menu bar for the window.
|
||||
func (w *WebviewWindow) HideMenuBar() {
|
||||
if w.impl == nil || w.isDestroyed() {
|
||||
return
|
||||
}
|
||||
InvokeSync(w.impl.hideMenuBar)
|
||||
}
|
||||
|
||||
// ToggleMenuBar toggles the menu bar for the window.
|
||||
func (w *WebviewWindow) ToggleMenuBar() {
|
||||
if w.impl == nil || w.isDestroyed() {
|
||||
return
|
||||
}
|
||||
InvokeSync(w.impl.toggleMenuBar)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1438,3 +1438,7 @@ func (w *macosWebviewWindow) delete() {
|
|||
|
||||
func (w *macosWebviewWindow) redo() {
|
||||
}
|
||||
|
||||
func (w *macosWebviewWindow) showMenuBar() {}
|
||||
func (w *macosWebviewWindow) hideMenuBar() {}
|
||||
func (w *macosWebviewWindow) toggleMenuBar() {}
|
||||
|
|
|
|||
|
|
@ -422,3 +422,7 @@ func (w *linuxWebviewWindow) isIgnoreMouseEvents() bool {
|
|||
func (w *linuxWebviewWindow) setIgnoreMouseEvents(ignore bool) {
|
||||
w.ignoreMouse(w.ignoreMouseEvents)
|
||||
}
|
||||
|
||||
func (w *linuxWebviewWindow) showMenuBar() {}
|
||||
func (w *linuxWebviewWindow) hideMenuBar() {}
|
||||
func (w *linuxWebviewWindow) toggleMenuBar() {}
|
||||
|
|
|
|||
|
|
@ -1169,9 +1169,37 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp
|
|||
w.parent.emit(events.Windows.WindowBackgroundErase)
|
||||
return 1 // Let WebView2 handle background erasing
|
||||
// Check for keypress
|
||||
case w32.WM_KEYDOWN:
|
||||
w.processKeyBinding(uint(wparam))
|
||||
case w32.WM_SYSCOMMAND:
|
||||
switch wparam {
|
||||
case w32.SC_KEYMENU:
|
||||
if lparam == 0 {
|
||||
// F10 or plain Alt key
|
||||
if w.processKeyBinding(w32.VK_F10) {
|
||||
return 0
|
||||
}
|
||||
} else {
|
||||
// Alt + key combination
|
||||
// The character code is in the low word of lparam
|
||||
char := byte(lparam & 0xFF)
|
||||
// Convert ASCII to virtual key code if needed
|
||||
vkey := w32.VkKeyScan(uint16(char))
|
||||
if w.processKeyBinding(uint(vkey)) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
case w32.WM_SYSKEYDOWN:
|
||||
globalApplication.info("w32.WM_SYSKEYDOWN: %v", uint(wparam))
|
||||
w.parent.emit(events.Windows.WindowKeyDown)
|
||||
if w.processKeyBinding(uint(wparam)) {
|
||||
return 0
|
||||
}
|
||||
case w32.WM_SYSKEYUP:
|
||||
w.parent.emit(events.Windows.WindowKeyUp)
|
||||
case w32.WM_KEYDOWN:
|
||||
globalApplication.info("w32.WM_KEYDOWN: %v", uint(wparam))
|
||||
w.parent.emit(events.Windows.WindowKeyDown)
|
||||
w.processKeyBinding(uint(wparam))
|
||||
case w32.WM_KEYUP:
|
||||
w.parent.emit(events.Windows.WindowKeyUp)
|
||||
case w32.WM_SIZE:
|
||||
|
|
@ -1917,6 +1945,43 @@ func (w *windowsWebviewWindow) setMinimiseButtonEnabled(enabled bool) {
|
|||
w.setStyle(enabled, w32.WS_MINIMIZEBOX)
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) toggleMenuBar() {
|
||||
if w.menu != nil {
|
||||
if w32.GetMenu(w.hwnd) == 0 {
|
||||
w32.SetMenu(w.hwnd, w.menu.menu)
|
||||
} else {
|
||||
w32.SetMenu(w.hwnd, 0)
|
||||
}
|
||||
|
||||
// Get the bounds of the client area
|
||||
//bounds := w32.GetClientRect(w.hwnd)
|
||||
|
||||
// Resize the webview
|
||||
w.chromium.Resize()
|
||||
|
||||
// Update size of webview
|
||||
w.update()
|
||||
// Restore focus to the webview after toggling menu
|
||||
w.focus()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) enableRedraw() {
|
||||
w32.SendMessage(w.hwnd, w32.WM_SETREDRAW, 1, 0)
|
||||
w32.RedrawWindow(w.hwnd, nil, 0, w32.RDW_ERASE|w32.RDW_FRAME|w32.RDW_INVALIDATE|w32.RDW_ALLCHILDREN)
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) disableRedraw() {
|
||||
w32.SendMessage(w.hwnd, w32.WM_SETREDRAW, 0, 0)
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) disableRedrawWithCallback(callback func()) {
|
||||
w.disableRedraw()
|
||||
callback()
|
||||
w.enableRedraw()
|
||||
|
||||
}
|
||||
|
||||
func NewIconFromResource(instance w32.HINSTANCE, resId uint16) (w32.HICON, error) {
|
||||
var err error
|
||||
var result w32.HICON
|
||||
|
|
@ -1986,3 +2051,15 @@ func (w *windowsWebviewWindow) setPadding(padding edge.Rect) {
|
|||
}
|
||||
w.chromium.SetPadding(padding)
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) showMenuBar() {
|
||||
if w.menu != nil {
|
||||
w32.SetMenu(w.hwnd, w.menu.menu)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) hideMenuBar() {
|
||||
if w.menu != nil {
|
||||
w32.SetMenu(w.hwnd, 0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ type Window interface {
|
|||
HandleWindowEvent(id uint)
|
||||
Height() int
|
||||
Hide() Window
|
||||
HideMenuBar()
|
||||
ID() uint
|
||||
Info(message string, args ...any)
|
||||
IsFocused() bool
|
||||
|
|
@ -46,7 +47,6 @@ type Window interface {
|
|||
OnWindowEvent(eventType events.WindowEventType, callback func(event *WindowEvent)) func()
|
||||
OpenContextMenu(data *ContextMenuData)
|
||||
Position() (int, int)
|
||||
RegisterContextMenu(name string, menu *Menu)
|
||||
RelativePosition() (int, int)
|
||||
Reload()
|
||||
Resizable() bool
|
||||
|
|
@ -70,10 +70,12 @@ type Window interface {
|
|||
SetURL(s string) Window
|
||||
SetZoom(magnification float64) Window
|
||||
Show() Window
|
||||
ShowMenuBar()
|
||||
Size() (width int, height int)
|
||||
OpenDevTools()
|
||||
ToggleFullscreen()
|
||||
ToggleMaximise()
|
||||
ToggleMenuBar()
|
||||
UnFullscreen()
|
||||
UnMaximise()
|
||||
UnMinimise()
|
||||
|
|
|
|||
|
|
@ -608,6 +608,36 @@ const (
|
|||
WM_DPICHANGED = 0x02E0
|
||||
)
|
||||
|
||||
const (
|
||||
SC_SIZE = 0xF000 // Resize the window
|
||||
SC_MOVE = 0xF010 // Move the window
|
||||
SC_MINIMIZE = 0xF020 // Minimize the window
|
||||
SC_MAXIMIZE = 0xF030 // Maximize the window
|
||||
SC_NEXTWINDOW = 0xF040 // Move to next window
|
||||
SC_PREVWINDOW = 0xF050 // Move to previous window
|
||||
SC_CLOSE = 0xF060 // Close the window
|
||||
SC_VSCROLL = 0xF070 // Vertical scroll
|
||||
SC_HSCROLL = 0xF080 // Horizontal scroll
|
||||
SC_MOUSEMENU = 0xF090 // Mouse menu
|
||||
SC_KEYMENU = 0xF100 // Key menu (triggered by Alt or F10)
|
||||
SC_ARRANGE = 0xF110 // Arrange windows
|
||||
SC_RESTORE = 0xF120 // Restore window from minimized/maximized
|
||||
SC_TASKLIST = 0xF130 // Task list
|
||||
SC_SCREENSAVE = 0xF140 // Screen saver
|
||||
SC_HOTKEY = 0xF150 // Hotkey
|
||||
SC_DEFAULT = 0xF160 // Default command
|
||||
SC_MONITORPOWER = 0xF170 // Monitor power
|
||||
SC_CONTEXTHELP = 0xF180 // Context help
|
||||
SC_SEPARATOR = 0xF00F // Separator
|
||||
)
|
||||
|
||||
const (
|
||||
// Remove the Close option from the window menu
|
||||
SC_MASK_CLOSE = ^uint16(SC_CLOSE)
|
||||
// Mask for extracting the system command
|
||||
SC_MASK_CMD = 0xFFF0
|
||||
)
|
||||
|
||||
// WM_ACTIVATE
|
||||
const (
|
||||
WA_INACTIVE = 0
|
||||
|
|
|
|||
|
|
@ -12,23 +12,24 @@ import (
|
|||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
SC_CLOSE = 0xF060
|
||||
SC_MOVE = 0xF010
|
||||
SC_MAXIMIZE = 0xF030
|
||||
SC_MINIMIZE = 0xF020
|
||||
SC_SIZE = 0xF000
|
||||
SC_RESTORE = 0xF120
|
||||
)
|
||||
|
||||
var (
|
||||
user32 = syscall.NewLazyDLL("user32.dll")
|
||||
getSystemMenu = user32.NewProc("GetSystemMenu")
|
||||
getMenuProc = user32.NewProc("GetMenu")
|
||||
enableMenuItem = user32.NewProc("EnableMenuItem")
|
||||
findWindow = user32.NewProc("FindWindowW")
|
||||
sendMessage = user32.NewProc("SendMessageW")
|
||||
vkKeyScan = user32.NewProc("VkKeyScanW") // Use W version for Unicode
|
||||
)
|
||||
|
||||
func VkKeyScan(ch uint16) uint16 {
|
||||
ret, _, _ := syscall.SyscallN(
|
||||
vkKeyScan.Addr(),
|
||||
uintptr(ch),
|
||||
)
|
||||
return uint16(ret)
|
||||
}
|
||||
|
||||
const (
|
||||
WMCOPYDATA_SINGLE_INSTANCE_DATA = 1542
|
||||
)
|
||||
|
|
@ -345,3 +346,9 @@ func SendMessageToWindow(hwnd HWND, msg string) {
|
|||
uintptr(unsafe.Pointer(&cds)),
|
||||
)
|
||||
}
|
||||
|
||||
// GetMenu retrieves a handle to the menu assigned to the specified window
|
||||
func GetMenu(hwnd HWND) HMENU {
|
||||
ret, _, _ := getMenuProc.Call(hwnd)
|
||||
return ret
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue