Skip to content

Commit 41f7e10

Browse files
committed
WinUIBackend: Implement file open/save picker support
Required wrapping IInitializeWithWindow (kinda horrible, but luckily issue 199 on thebrowsercompany/swift-winrt had most of the information I needed to piece things together)
1 parent d351f4b commit 41f7e10

File tree

4 files changed

+67
-4
lines changed

4 files changed

+67
-4
lines changed

Package.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,11 +238,16 @@ let package = Package(
238238
name: "WinUIBackend",
239239
dependencies: [
240240
"SwiftCrossUI",
241+
"WinUIInterop",
241242
.product(name: "WinUI", package: "swift-winui"),
242243
.product(name: "WinAppSDK", package: "swift-windowsappsdk"),
243244
.product(name: "WindowsFoundation", package: "swift-windowsfoundation"),
244245
]
245246
),
247+
.target(
248+
name: "WinUIInterop",
249+
dependencies: []
250+
),
246251
// .target(
247252
// name: "CursesBackend",
248253
// dependencies: ["SwiftCrossUI", "TermKit"]

Sources/WinUIBackend/WinUIBackend.swift

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import WinAppSDK
66
import WinUI
77
import WindowsFoundation
88
import WinSDK
9+
import WinUIInterop
910

1011
// Many force tries are required for the WinUI backend but we don't really want them
1112
// anywhere else so just disable them for this file.
@@ -1005,8 +1006,14 @@ public final class WinUIBackend: AppBackend {
10051006
resultHandler handleResult: @escaping (DialogResult<[URL]>) -> Void
10061007
) {
10071008
let picker = FileOpenPicker()
1008-
// TODO: Associate the picker with a window. Requires some janky WinUI
1009-
// Win32 interop kinda stuff I believe.
1009+
1010+
let window = window ?? windows[0]
1011+
let hwnd = window.getHWND()!
1012+
let interface: SwiftIInitializeWithWindow = try! picker.thisPtr.QueryInterface()
1013+
try! interface.initialize(with: hwnd)
1014+
1015+
picker.fileTypeFilter.append("*")
1016+
10101017
if openDialogOptions.allowMultipleSelections {
10111018
let promise = try! picker.pickMultipleFilesAsync()!
10121019
promise.completed = { operation, status in
@@ -1015,9 +1022,14 @@ public final class WinUIBackend: AppBackend {
10151022
let operation,
10161023
let result = try? operation.getResults()
10171024
else {
1025+
handleResult(.cancelled)
10181026
return
10191027
}
1020-
print(result)
1028+
1029+
let files = Array(result).compactMap { $0 }
1030+
.map(\.path)
1031+
.map(URL.init(fileURLWithPath:))
1032+
handleResult(.success(files))
10211033
}
10221034
} else {
10231035
let promise = try! picker.pickSingleFileAsync()!
@@ -1027,9 +1039,12 @@ public final class WinUIBackend: AppBackend {
10271039
let operation,
10281040
let result = try? operation.getResults()
10291041
else {
1042+
handleResult(.cancelled)
10301043
return
10311044
}
1032-
print(result)
1045+
1046+
let file = URL(fileURLWithPath: result.path)
1047+
handleResult(.success([file]))
10331048
}
10341049
}
10351050
}
@@ -1040,6 +1055,28 @@ public final class WinUIBackend: AppBackend {
10401055
window: Window?,
10411056
resultHandler handleResult: @escaping (DialogResult<URL>) -> Void
10421057
) {
1058+
let picker = FileSavePicker()
1059+
1060+
let window = window ?? windows[0]
1061+
let hwnd = window.getHWND()!
1062+
let interface: SwiftIInitializeWithWindow = try! picker.thisPtr.QueryInterface()
1063+
try! interface.initialize(with: hwnd)
1064+
1065+
_ = picker.fileTypeChoices.insert("Text", [".txt"].toVector())
1066+
let promise = try! picker.pickSaveFileAsync()!
1067+
promise.completed = { operation, status in
1068+
guard
1069+
status == .completed,
1070+
let operation,
1071+
let result = try? operation.getResults()
1072+
else {
1073+
handleResult(.cancelled)
1074+
return
1075+
}
1076+
1077+
let file = URL(fileURLWithPath: result.path)
1078+
handleResult(.success(file))
1079+
}
10431080
}
10441081

10451082
public func createTapGestureTarget(wrapping child: Widget, gesture: TapGesture) -> Widget {
@@ -1215,6 +1252,23 @@ final class TapGestureTarget: WinUI.Canvas {
12151252
var child: WinUI.FrameworkElement?
12161253
}
12171254

1255+
class SwiftIInitializeWithWindow: WindowsFoundation.IUnknown {
1256+
override class var IID: WindowsFoundation.IID {
1257+
WindowsFoundation.IID(
1258+
Data1: 0x3E68D4BD,
1259+
Data2: 0x7135,
1260+
Data3: 0x4D10,
1261+
Data4: (0x80, 0x18, 0x9F, 0xB6, 0xD9, 0xF3, 0x3F, 0xA1)
1262+
)
1263+
}
1264+
1265+
func initialize(with hwnd: HWND) throws {
1266+
_ = try perform(as: IInitializeWithWindow.self) { pThis in
1267+
try CHECKED(pThis.pointee.lpVtbl.pointee.Initialize(pThis, hwnd))
1268+
}
1269+
}
1270+
}
1271+
12181272
public class CustomWindow: WinUI.Window {
12191273
/// Hardcoded menu bar height from MenuBar_themeresources.xaml in the
12201274
/// microsoft-ui-xaml repository.

Sources/WinUIInterop/dummy.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#include "WinUIInterop.h"
2+
#include <ShObjIdl.h>
3+
#include <Windows.h>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)