thebrowsercompany / windows-samples

Sample Apps for Swift on Windows
MIT License
262 stars 13 forks source link

Usability issues #14

Open bgbernovici opened 6 months ago

bgbernovici commented 6 months ago

Hi, thank you for sharing these samples. They have been immensely helpful in getting me started with Swift and WinRT. However, I seem to be encountering a few challenges, and I was hoping to get some clarification on certain aspects. Specifically:

Application.current.resources.insert("pointerEnteredGradientBrush", pointerEnteredGradientBrush)

lazy var controlTemplate = XamlReader.load(""" <ControlTemplate TargetType="GridViewItem" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    </ControlTemplate>

""") lazy var itemContainerStyle = { var style = Style() style.targetType = TypeName(name: "Microsoft.UI.Xaml.Controls.GridViewItem", kind: TypeKind.primitive) style.setters.append(Setter(GridViewItem.templateProperty, controlTemplate)) return style }()

- I'm struggling to update UI values upon data change events. I have read about how state and data binding works [here](https://learn.microsoft.com/en-us/windows/uwp/data-binding/data-binding-in-depth), but I cannot get it to work. I've tried deriving my class from DependencyObject and I've also tried implementing INotifyPropertyChanged. With the latter the issue is that I do not know exactly how to replace the event delegate that is used in C#, my class looks something like that, but propertyChanged only gives me the addHandler and removeHandler which I do not know exactly how should they be used in tandem with didSet. In C# it seems pretty straightforward how it works by invoking it for each member:

public class Vehicle: INotifyPropertyChanged {

var name: String

public var propertyChanged: Event = Event( add: { in return EventRegistrationToken() }, remove: { in } ) .... }

And then I assume that the data binding on the TextBlock control should work in the following way? Or should it be defined like a data source for the item container?
lazy var nameTextBlock: TextBlock =  {
    let name = TextBlock()
    name.textWrapping = .wrap
    name.textTrimming = .wordEllipsis
    name.fontWeight = FontWeights.bold
    name.textDecorations = TextDecorations.underline
    name.fontSize = 14
    name.maxHeight = 20
    name.foreground = SolidColorBrush(Color(a:255, r:255,  g:255,  b:255))

    var b: Binding = Binding()
    b.mode = BindingMode.oneWay
    b.source = vehicle.name
    try! name.setBinding(TextBlock.textProperty, b)

    return name
}()


Thank you,
Bogdan
paytontech commented 6 months ago

I'm also unable to navigate via frame!

bgbernovici commented 6 months ago

I gave it another try this evening. After reading about property wrappers, I ended up with this:

public class Vehicle: INotifyPropertyChanged  {

    private var names: String
    public var namesHandler: String {
        get {return self.names}
        set {
            self.names = newValue
            self._propertyChanged.invoke(self, PropertyChangedEventArgs("namesHandler"))
        }
    }

    @EventSource<PropertyChangedEventHandler> public var propertyChanged: Event<PropertyChangedEventHandler>
}

And the binding is:

var b: Binding = Binding()
b.mode = BindingMode,oneWay
b.source = container.namesHandler
b.updateSourceTrigger = .propertyChanged
try! names.setBinding(TextBlock.textProperty, b)

But it still does not update the TextBlock. I've also tried using PropertyPath after setting the DataContext without any luck. I assume that it stems from the fact that my variables in the Swift class are not bindable.

ducaale commented 6 months ago

I would also like to know if there is a way to do navigation using Frame and Page classes.

By the way, you can use Swift's Observation Framework as an alternative to data binding. For example, given the following button that tracks changes to its content:

Expand for a cybernetically enhanced button + helpers ```swift extension Button { convenience init( content: @autoclosure @escaping () -> String, onClick: (() -> ())? = nil ) { self.init() self.updateContent(content) if let onClick = onClick { self.click.addHandler { _, _ in onClick() } } } func updateContent(_ content: @escaping () -> String) { withObservationTracking { self.content = content() } onChange: { // We need to defer updating content since this closure is executed **before** the observed state // has been updated. // // Also, `onChange` is invoked once for each `withObservationTracking` call so we need to // recursively call `updateContent`. guard let dispatcherQueue = WinAppSDK.DispatcherQueue.getForCurrentThread() else { return } let _ = try! dispatcherQueue.tryEnqueue { self.updateContent(content) } } } } // -------------- More helpers for SwiftUI-like experience ------------- extension StackPanel { convenience init(content: () -> UIElement) { self.init() self.children.append(content()) } } extension Window { convenience init(content: (() -> UIElement)? = nil) { self.init() if let content = content { self.content = content() } } } extension FrameworkElement { func verticalAlignment(_ alignment: VerticalAlignment) -> Self { self.verticalAlignment = alignment return self } func horizontalAlignment(_ alignment: HorizontalAlignment) -> Self { self.horizontalAlignment = alignment return self } } ```

You can use it like this:

import Foundation
import UWP
import WinAppSDK
import WindowsFoundation
import WinUI
import Observation

@Observable class State {
    var counter: Int = 0
}

@main
public class PreviewApp: SwiftApplication {
    var state = State()

    lazy var window = Window {
        StackPanel {
            Button(content: "clicked \(self.state.counter) times") {
                self.state.counter += 1
            }
        }
        .horizontalAlignment(.center)
        .verticalAlignment(.center)
    }

    override public func onLaunched(_ args: WinUI.LaunchActivatedEventArgs) {
        try! window.activate()
    }
}
bgbernovici commented 6 months ago

Thank you for the suggestion @ducaale. I will definitely try it out.

vetledv commented 5 months ago

Hey, I am also struggling with navigation using Frame. Did anyone figure this out? 😄

litewrap commented 3 months ago

Fantastic work, thanks to @ducaale your suggestion work like a charm ! Looking for more examples with helpers for SwiftUI-like experience.