Accessing XAML (WPF) elements in F#

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

Answers (2)

annoying_squid
annoying_squid

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

FoggyFinder
FoggyFinder

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

Picture

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

Related Questions