Reputation: 11595
Why isn't my ValueConverter being triggered?
My value-converter is triggered when the constructor of my view-model is executed. However, it doesn't get triggered when I assign a new value to the Cells property.
I expect this line to trigger my value-converter update:
this.Cells <- grid |> cycleThroughCells
|> Map.toSeq
|> Seq.map snd
|> Seq.toList
But it doesn't.
I have the following 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 @>)
My ViewModelBase is the following:
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
My XAML is the following:
<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>
My ValueConverter is the following:
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"
Upvotes: 2
Views: 241
Reputation: 2220
When I try to run this code I got the exception. Not sure, this may be due, but this code works for me without errors:
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
I changed your Xaml, because I don't think to describe 36 rectangles is a good idea. One option is to use a 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>
Thus you can create grid of different dimensions depending on the value of rowCount.
I added a variable "count" to show that the function is called with the specified interval, even though the picture does not change.
Result:
Upvotes: 4
Reputation: 31
I believe the problem lies in the fact that you're binding to individual elements of the list, but firing the NotifyPropertyChanged for the list itself. Have you tried replacing the list with an ObservableCollection and using Clear
+ AddRange
to repopulate it on each tick?
Upvotes: 2