私はMVRアーキテクチャに準拠したXamarin Forms Map
クラスの拡張を行っています。ここでは派生型である:私の見解ではReactUI for Xamarin Forms:カスタムBindablePropertyに対して双方向バインディングが機能しない
type GeographicMap() =
inherit Map()
static let centerProperty = BindableProperty.Create("Center", typeof<GeodesicLocation>, typeof<GeographicMap>, new GeodesicLocation())
static let radiusProperty = BindableProperty.Create("Radius", typeof<float>, typeof<GeographicMap>, 1.0)
member this.Radius
with get() = 1.0<km> * (this.GetValue(radiusProperty) :?> float)
and set(value: float<km>) = if not <| value.Equals(this.Radius) then this.SetValue(radiusProperty, value/1.0<km>)
member this.Center
with get() = this.GetValue(centerProperty) :?> GeodesicLocation
and set(value: GeodesicLocation) = if not <| value.Equals(this.Center) then this.SetValue(centerProperty, value)
override this.OnPropertyChanged(propertyName) =
match propertyName with
| "VisibleRegion" ->
this.Center <- this.VisibleRegion.Center |> XamarinGeographic.geodesicLocation
this.Radius <- this.VisibleRegion.Radius |> XamarinGeographic.geographicDistance
| "Radius" | "Center" ->
match box this.VisibleRegion with
| null -> this.MoveToRegion(MapSpan.FromCenterAndRadius(this.Center |> XamarinGeographic.position, this.Radius |> XamarinGeographic.distance))
| _ ->
let existingCenter, existingRadius = this.VisibleRegion.Center |> XamarinGeographic.geodesicLocation, this.VisibleRegion.Radius |> XamarinGeographic.geographicDistance
let deltaCenter, deltaRadius = Geodesic.WGS84.Distance existingCenter (this.Center), existingRadius - this.Radius
let threshold = 0.1 * this.Radius
if Math.Abs(deltaRadius/1.0<km>) > threshold/1.0<km> || Math.Abs((deltaCenter |> UnitConversion.kilometres)/1.0<km>) > threshold/1.0<km> then
this.MoveToRegion(MapSpan.FromCenterAndRadius(this.Center |> XamarinGeographic.position, this.Radius |> XamarinGeographic.distance))
| _ -> propertyName |> ignore
次のように、私はCenter
財産と私のViewModelのLocation
プロパティ間のバインディングを追加しました:私は結合双方向を持って
type DashboardView(theme: Theme) as this =
inherit ContentPage<DashboardViewModel, DashboardView>(theme)
new() = new DashboardView(Themes.AstridTheme)
override __.CreateContent() =
theme.GenerateGrid([|"Auto"; "*"|], [|"*"|]) |> withColumn(
[|
theme.VerticalLayout() |> withBlocks(
[|
theme.GenerateLabel(fun l -> this.Title <- l)
|> withAlignment LayoutOptions.Center LayoutOptions.Center
|> withOneWayBinding(this.ViewModel, this, <@ fun (vm: DashboardViewModel) -> vm.Title @>, <@ fun (v: DashboardView) -> (v.Title: Label).Text @>)
theme.GenerateSearchBar(fun sb -> this.AddressSearchBar <- sb)
|> withSearchBarPlaceholder LocalisedStrings.SearchForAPlaceOfInterest
|> withTwoWayBinding(this.ViewModel, this, <@ fun (vm: DashboardViewModel) -> vm.SearchAddress @>, <@ fun (v: DashboardView) -> (v.AddressSearchBar: SearchBar).Text @>)
|> withSearchCommand this.ViewModel.SearchForAddress
|])
theme.GenerateMap(fun m -> this.Map <- m)
|> withTwoWayBinding(this.ViewModel, this, <@ fun (vm: DashboardViewModel) -> vm.Location @>, <@ fun (v:DashboardView) -> (v.Map: GeographicMap).Center @>)
|]) |> createFromColumns :> View
member val AddressSearchBar = Unchecked.defaultof<SearchBar> with get, set
member val Title = Unchecked.defaultof<Label> with get, set
member val Map = Unchecked.defaultof<GeographicMap> with get, set
お知らせDashboardViewModel.Location
とDashboardView.Map.Center
の間です。私はまた、DashboardViewModel.SearchAddress
とDashboardView.AddressSearchBar.Text
の間に双方向バインディングを持っています。後者のバインディングは機能します。前者はそうではない。私はバインド可能なプロパティGeographicMap.Center
を正しく設定していないため、これが必要であると仮定します。
マップをパンするとVisibleRegion
プロパティが変更され、Center
プロパティの更新がトリガーされるため、双方向バインディングが機能しないことがわかりました。しかし、私のViewModelクラスに:検索テキストが更新されるたびにマップがパンされるときLocation
セッターがヒットされませんが
type DashboardViewModel(?host: IScreen, ?platform: IPlatform) as this =
inherit ReactiveViewModel()
let host, platform = LocateIfNone host, LocateIfNone platform
let searchResults = new ObservableCollection<GeodesicLocation>()
let commandSubscriptions = new CompositeDisposable()
let geocodeAddress(vm: DashboardViewModel) =
let vm = match box vm with | null -> this | _ -> vm
searchResults.Clear()
async {
let! results = platform.Geocoder.GetPositionsForAddressAsync(vm.SearchAddress) |> Async.AwaitTask
results |> Seq.map (fun r -> new GeodesicLocation(r.Latitude * 1.0<deg>, r.Longitude * 1.0<deg>)) |> Seq.iter searchResults.Add
match results |> Seq.tryLast with
| Some position -> return position |> XamarinGeographic.geodesicLocation |> Some
| None -> return None
} |> Async.StartAsTask
let searchForAddress = ReactiveCommand.CreateFromTask geocodeAddress
let mutable searchAddress = String.Empty
let mutable location = new GeodesicLocation(51.4<deg>, 0.02<deg>)
override this.SubscribeToCommands() = searchForAddress.ObserveOn(RxApp.MainThreadScheduler).Subscribe(fun res -> match res with | Some l -> this.Location <- l | None -> res |> ignore) |> commandSubscriptions.Add
override __.UnsubscribeFromCommands() = commandSubscriptions.Clear()
member __.Title with get() = LocalisedStrings.AppTitle
member __.SearchForAddress with get() = searchForAddress
member this.SearchAddress
with get() = searchAddress
// GETS HIT WHEN SEARCH TEXT CHANGES
and set(value) = this.RaiseAndSetIfChanged(&searchAddress, value, "SearchAddress") |> ignore
member this.Location
with get() = location
// DOES NOT GET HIT WHEN THE MAP GETS PANNED, TRIGGERING AN UPDATE OF ITS Center PROPERTY
and set(value) = this.RaiseAndSetIfChanged(&location, value, "Location") |> ignore
interface IRoutableViewModel with
member __.HostScreen = host
member __.UrlPathSegment = "Dashboard"
SearchAddress
セッターは、そのCenter
プロパティの更新を引き起こし、ヒットを取得します。
バインド可能なCenter
プロパティの設定に関連するものがありませんか?
UPDATE:これは、ReactiveUIのWhenAnyValue
拡張機能と関係があります。これは、自分のバインディングで内部的に使用されています。これを実証するために、私は、ビューの作成に数行を追加しました:
override __.CreateContent() =
let result =
theme.GenerateGrid([|"Auto"; "*"|], [|"*"|]) |> withColumn(
[|
theme.VerticalLayout() |> withBlocks(
[|
theme.GenerateLabel(fun l -> this.Title <- l)
|> withAlignment LayoutOptions.Center LayoutOptions.Center
|> withOneWayBinding(this.ViewModel, this, <@ fun (vm: DashboardViewModel) -> vm.Title @>, <@ fun (v: DashboardView) -> (v.Title: Label).Text @>)
theme.GenerateSearchBar(fun sb -> this.AddressSearchBar <- sb)
|> withSearchBarPlaceholder LocalisedStrings.SearchForAPlaceOfInterest
|> withTwoWayBinding(this.ViewModel, this, <@ fun (vm: DashboardViewModel) -> vm.SearchAddress @>, <@ fun (v: DashboardView) -> (v.AddressSearchBar: SearchBar).Text @>)
|> withSearchCommand this.ViewModel.SearchForAddress
|])
theme.GenerateMap(fun m -> this.Map <- m)
|> withTwoWayBinding(this.ViewModel, this, <@ fun (vm: DashboardViewModel) -> vm.Location @>, <@ fun (v:DashboardView) -> (v.Map: GeographicMap).Center @>)
|]) |> createFromColumns :> View
this.WhenAnyValue(ExpressionConversion.toLinq <@ fun (v:DashboardView) -> (v.Map: GeographicMap).Center @>).ObserveOn(RxApp.MainThreadScheduler).Subscribe(fun (z) ->
z |> ignore) |> ignore // This breakpoint doesn't get hit when the map pans.
this.WhenAnyValue(ExpressionConversion.toLinq <@ fun (v:DashboardView) -> (v.AddressSearchBar: SearchBar).Text @>).ObserveOn(RxApp.MainThreadScheduler).Subscribe(fun (z) ->
z |> ignore) |> ignore // This breakpoint gets hit when text is changed in the search bar.
result
これは良いヒントです。それは私が確かに実装するものです。すぐに問題が解決しなくても、既存のアーキテクチャの改善です。 –
私は変更を加え、それに応じて質問を編集しました。私が疑っているように、それは問題を修正していませんが、それはコードを整理するのを助けました。提案していただきありがとうございます。 –