【问题标题】:Why isn't my ValueConverter being triggered?为什么我的 ValueConverter 没有被触发?
【发布时间】:2016-03-19 16:54:17
【问题描述】:

为什么我的 ValueConverter 没有被触发?

当我的视图模型的构造函数被执行时,我的值转换器被触发。但是,当我为 Cells 属性分配新值时,它不会被触发。

我希望这条线会触发我的值转换器更新:

this.Cells <- grid |> cycleThroughCells
                   |> Map.toSeq
                   |> Seq.map snd
                   |> Seq.toList

但事实并非如此。

我有以下 ViewModel:

type ViewModel() as this =
    inherit ViewModelBase()

    let rowCount = 6
    let mutable grid = rowCount |> createGrid
                                |> setCell { X=3; Y=1; State=Alive }
                                |> setCell { X=3; Y=0; State=Alive }
                                |> setCell { X=4; Y=1; State=Alive }

    let mutable _cells = grid |> Map.toSeq
                              |> Seq.map snd
                              |> Seq.toList
    let cycleHandler _ = 

        this.Cells <- grid |> cycleThroughCells
                           |> Map.toSeq
                           |> Seq.map snd
                           |> Seq.toList
    member this.Play =
        DelegateCommand ((fun _ -> let timer = createTimer 500 cycleHandler
                                   do while true do
                                      do Async.RunSynchronously timer), fun _ -> true) :> ICommand
    member this.Cells
        with get() = _cells 
        and set(value) =
            _cells <- value
            base.NotifyPropertyChanged(<@ this.Cells @>)

我的 ViewModelBase 如下:

open System.ComponentModel
open Microsoft.FSharp.Quotations.Patterns

type ViewModelBase () =
    let propertyChanged = 
        Event<PropertyChangedEventHandler,PropertyChangedEventArgs>()

    let getPropertyName = function 
        | PropertyGet(_,pi,_) -> pi.Name
        | _ -> invalidOp "Expecting property getter expression"

    interface INotifyPropertyChanged with
        [<CLIEvent>]
        member this.PropertyChanged = propertyChanged.Publish

    member private this.NotifyPropertyChanged propertyName = 
        propertyChanged.Trigger(this,PropertyChangedEventArgs(propertyName))

    member this.NotifyPropertyChanged quotation = 
        quotation |> getPropertyName |> this.NotifyPropertyChanged

我的 XAML 如下:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Client;assembly=Client"
        Background="Black"
        Title="Game of Life" Height="450" Width="500">

    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>

    <Window.Resources>
        <local:StateToBrushConverter x:Key="StateToBrushConverter" />
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />

            <RowDefinition />
            <RowDefinition />
            <RowDefinition />

            <RowDefinition />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />

            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <Rectangle Grid.Row="0" Grid.Column="0" Fill="{Binding Cells[0], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="0" Grid.Column="1" Fill="{Binding Cells[1], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="0" Grid.Column="2" Fill="{Binding Cells[2], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="0" Grid.Column="3" Fill="{Binding Cells[3], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="0" Grid.Column="4" Fill="{Binding Cells[4], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="0" Grid.Column="5" Fill="{Binding Cells[5], Converter={StaticResource StateToBrushConverter}}" />

        <Rectangle Grid.Row="1" Grid.Column="0" Fill="{Binding Cells[6], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="1" Grid.Column="1" Fill="{Binding Cells[7], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="1" Grid.Column="2" Fill="{Binding Cells[8], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="1" Grid.Column="3" Fill="{Binding Cells[9], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="1" Grid.Column="4" Fill="{Binding Cells[10], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="1" Grid.Column="5" Fill="{Binding Cells[11], Converter={StaticResource StateToBrushConverter}}" />

        <Rectangle Grid.Row="2" Grid.Column="0" Fill="{Binding Cells[12], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="2" Grid.Column="1" Fill="{Binding Cells[13], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="2" Grid.Column="2" Fill="{Binding Cells[14], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="2" Grid.Column="3" Fill="{Binding Cells[15], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="2" Grid.Column="4" Fill="{Binding Cells[16], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="2" Grid.Column="5" Fill="{Binding Cells[17], Converter={StaticResource StateToBrushConverter}}" />

        <Rectangle Grid.Row="3" Grid.Column="0" Fill="{Binding Cells[18], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="3" Grid.Column="1" Fill="{Binding Cells[19], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="3" Grid.Column="2" Fill="{Binding Cells[20], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="3" Grid.Column="3" Fill="{Binding Cells[21], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="3" Grid.Column="4" Fill="{Binding Cells[22], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="3" Grid.Column="5" Fill="{Binding Cells[23], Converter={StaticResource StateToBrushConverter}}" />

        <Rectangle Grid.Row="4" Grid.Column="0" Fill="{Binding Cells[24], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="4" Grid.Column="1" Fill="{Binding Cells[25], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="4" Grid.Column="2" Fill="{Binding Cells[26], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="4" Grid.Column="3" Fill="{Binding Cells[27], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="4" Grid.Column="4" Fill="{Binding Cells[28], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="4" Grid.Column="5" Fill="{Binding Cells[29], Converter={StaticResource StateToBrushConverter}}" />

        <Rectangle Grid.Row="5" Grid.Column="0" Fill="{Binding Cells[30], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="5" Grid.Column="1" Fill="{Binding Cells[31], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="5" Grid.Column="2" Fill="{Binding Cells[32], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="5" Grid.Column="3" Fill="{Binding Cells[33], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="5" Grid.Column="4" Fill="{Binding Cells[34], Converter={StaticResource StateToBrushConverter}}" />
        <Rectangle Grid.Row="5" Grid.Column="5" Fill="{Binding Cells[35], Converter={StaticResource StateToBrushConverter}}" />


        <Button Grid.Row="6" Grid.Column="1" Content="Go!" Command="{Binding Play}" />
    </Grid>

</Window>

我的 ValueConverter 如下:

type StateToBrushConverter() =
    interface IValueConverter with
        member x.Convert(value, targetType, parameter, culture) = 
            let cell = value :?> Cell
            match cell.State with 
            | Alive -> SolidColorBrush(Colors.LightGreen) :> obj
            | Dead  -> SolidColorBrush(Colors.Black)      :> obj

        member x.ConvertBack(value, targetType, parameter, culture) = failwith "Not implemented yet"

【问题讨论】:

  • 这是非常非常糟糕的代码。你想做什么?
  • 但我强烈建议您使用FSharp.ViewModule.Core - 这样您就可以将苍蝇与肉饼分开 =)
  • @ScottNimrod 这是一本不同的书,你看了吗?它更接近 F#,并解释了如何从 lambda 演算转到使用 let 语句的 ML。如果有一本关于 OCAML 的书,例如 Think OCaml - How to Think Like a (Functional) Programmer,您应该能够看到 F# 语言是如何演变的,并更好地理解函数式程序如何更像数学而不是食谱。
  • @ScottNimrod 如果在@ 和 OP 名称之间放置一个空格,则不会通知 OP。由于空间原因,我没有收到任何通知。我只是通过检查这个问题才看到评论。
  • @ScottNimrod 绑定到集合中通常很奇怪 - 我会确保在主 (UI) 线程上调用你的 setter,看看这是否会有所不同。

标签: wpf f#


【解决方案1】:

当我尝试运行此代码时,我遇到了异常。 不确定,这可能是由于,但这段代码对我有用,没有错误:

 type MainViewModel() as self = 
    inherit ViewModelBase()   
     
    let rowCount = 6
    let mutable grid = rowCount |> createGrid
                                |> setCell { Y=2; X=1; State=Alive }
                                |> setCell { Y=2; X=2; State=Alive }
                                |> setCell { Y=3; X=2; State=Alive }
                                |> setCell { Y=3; X=3; State=Alive }

    let mutable _cells = grid |> Map.toSeq
                              |> Seq.map snd
                              |> Seq.toList
    
    let mutable count = 0
    let mutable isEnabled = true

    let cycleHandler _ = 
        _cells <- grid |> cycleThroughCells
                       |> Map.toSeq
                       |> Seq.map snd
                       |> Seq.toList
        count <- count + 1
        self.NotifyPropertyChanged(<@ self.Cells @>)
        self.NotifyPropertyChanged(<@ self.Count @>)
       
    let change _ = 
        isEnabled <- false
        self.NotifyPropertyChanged(<@ self.IsEnabled @>)
        
        async
            {
                while true do
                    do! Async.Sleep 2000
                    cycleHandler()
            }
        |> Async.Start

    member __.Play = DelegateCommand (change, fun _ -> isEnabled) :> ICommand
    member __.N = rowCount
    member __.Cells = _cells
    member __.Count = count
    member __.IsEnabled = isEnabled

我更改了您的 Xaml,因为我认为描述 36 个矩形不是一个好主意。 一种选择是使用 ListBox:

<Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <Button Grid.Row="0" Content="Go!" Command="{Binding Play}" IsEnabled="{Binding IsEnabled}"  Margin="5"  HorizontalAlignment="Right" />
        <TextBlock Grid.Row="0" Text="{Binding Count}"  Margin="5" HorizontalAlignment="Left"></TextBlock>
        <ListBox Grid.Row="1" Margin="2" ItemsSource="{Binding Cells}" IsEnabled="False"
            HorizontalContentAlignment="Stretch"
            VerticalContentAlignment="Stretch">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid Rows="{Binding N}" Columns="{Binding N}"/>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Rectangle Stretch="Fill"
                               Fill="{Binding Converter={StaticResource StateToBrushConverter}}"></Rectangle>
                </DataTemplate>
            </ListBox.ItemTemplate>
            <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="Padding" Value="0" />
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>
    </Grid>

因此,您可以根据 rowCount 的值创建不同尺寸的网格。

我添加了一个变量“count”来显示函数以指定的间隔调用,即使图片没有变化。

结果:

【讨论】:

    【解决方案2】:

    我认为问题在于您绑定到列表的各个元素,但为列表本身触发了 NotifyPropertyChanged。您是否尝试过用 ObservableCollection 替换列表并使用 Clear + AddRange 在每个刻度上重新填充它?

    【讨论】:

    • 谢谢。我尝试使用 ObservableCollection。但是,它并没有解决我的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-18
    • 1970-01-01
    • 2022-07-03
    • 2021-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多