Reputation: 349
I'm trying to create a reactive application in F# using WPF and I have encountered some problems with accessing XAML elements from the code.
In the XAML-file I have created a grid containing 16 columns and rows each:
<StackPanel Name="cellStackPanel">
<Grid Name="cellGrid" Height="500" Width="500" Margin="10,10,10,10" Background="#CCCCCC">
<Grid.Resources>
<Style TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border x:Name="border" Background="#FFFFFFFF" Margin="1,1,1,1">
<ContentPresenter x:Name="contentPresenter"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter Property="Background" TargetName="border" Value="Black"/>
</Trigger>
<Trigger Property="Control.IsMouseOver" Value="true">
<Setter Property="Background" TargetName="border" Value="#CCCCCC"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
</StackPanel>
In F#, I traverse through the grid and programatically initialize every cell into ToggleButtons:
let initCell (x,y) (grid : Grid) =
let cell = ToggleButton()
Grid.SetColumn(cell, x)
Grid.SetRow(cell, y)
ignore (grid.Children.Add(cell))
Now I want to create an observable (to wait for in a recursive asynchronous loop) for clicking any of my ToggleButtons. I can access the grid element itself within the code but my problem is that I don't know how to access its child elements that I've created programatically. I was thinking of a, perhaps rudimentary, solution which is to catch the click event from the entire grid, and at the same time get the mouse coordinates to calculate which cell was clicked. But that is probably not a good way to do this. I hope my question is understandable, otherwise, let me know.
Upvotes: 2
Views: 442
Reputation: 531
I'm not a big fan of 3rd party solutions as an answer. If I understand your question, you are trying to access an element that was specified in your XAML. This can be easily done by converting your XAML to a string type then using:
let xamlBytes = Encoding.UTF8.GetBytes(xamlString)
let xamlStream = new MemoryStream(xamlBytes)
let xamlRoot = XamlReader.Load(xamlStream)
let rootElement = xamlRoot :?> FrameworkElement
let elementYouNeed = rootElement.FindName("nameOfElementYouNeed") :?> TypeOfElementYouNeed
Your case might be slightly different but this is the general idea.
Upvotes: 0
Reputation: 2220
As I said in the comment easier to use FsXAML + FSharp.ViewModule.
I don't quite understand what you want to, so a simple example:
<Grid>
<ListBox
ItemsSource="{Binding Cells}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding N}" Columns="{Binding N}"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<ToggleButton Content="{Binding Text}"
IsChecked="{Binding IsChecked}"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}},
Path=DataContext.ClickCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=DataContext}"
>
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border x:Name="border" Background="Wheat" Margin="0">
<ContentPresenter x:Name="contentPresenter"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter Property="Background" TargetName="border" Value="DarkGreen"/>
</Trigger>
<Trigger Property="Control.IsMouseOver" Value="true">
<Setter Property="Background" TargetName="border" Value="#CCCCCC"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ToggleButton.Style>
</ToggleButton>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Padding" Value="0" />
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
You can use ItemsControl instead of ListBox, but configuring it will take some time =)
.fs:
type State = {mutable IsChecked:bool; Text:string}
type MainViewModel() as self =
inherit ViewModelBase()
let n = 16
let data = [1..n*n] |> List.map(fun i -> {IsChecked = false; Text = string i})
let click (state:obj) =
let st = (state :?> State)
MessageBox.Show(sprintf "%s %b" st.Text st.IsChecked ) |> ignore
let clickcommand = self.Factory.CommandSyncParam(click)
member __.ClickCommand = clickcommand
member __.Cells = data
member __.N = n
Thus, you can do anything with your object in the function click
.
P.S. I allowed myself to change a little your ToggleButton style to the picture was more clearer.
Upvotes: 2