2016-05-04 15 views
3

Ich versuche, eine reaktive Anwendung in F # mit WPF zu erstellen, und ich habe einige Probleme beim Zugriff auf XAML-Elemente aus dem Code festgestellt.Zugriff auf XAML (WPF) -Elemente in F #

In der XAML-Datei-I ein Raster mit 16 Spalten und Zeilen jeweils angelegt haben:

 <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 #, durchqueren I das Gitter durch und programmatisch jede Zelle in ToggleButtons initialisieren:

let initCell (x,y) (grid : Grid) = 
     let cell = ToggleButton() 
     Grid.SetColumn(cell, x) 
     Grid.SetRow(cell, y) 
     ignore (grid.Children.Add(cell)) 

Jetzt möchte ich eine Observable erstellen (um in einer rekursiven asynchronen Schleife zu warten), um auf eines meiner ToggleButtons zu klicken. Ich kann auf das Rasterelement selbst innerhalb des Codes zugreifen, aber mein Problem ist, dass ich nicht weiß, wie ich auf seine untergeordneten Elemente zugreifen kann, die ich programmatisch erstellt habe. Ich dachte an eine vielleicht rudimentäre Lösung, die das Klickereignis aus dem gesamten Raster abfängt und gleichzeitig die Mauskoordinaten ermittelt, um zu berechnen, auf welche Zelle geklickt wurde. Aber das ist wahrscheinlich kein guter Weg, dies zu tun. Ich hoffe meine Frage ist verständlich, ansonsten lass es mich wissen.

+0

Sie können 'grid.Children' bei Bedarf wiederholen und ToggleButtons – ASh

+0

recentive auch: es ist möglich, RowDefinitions und ColumnDefinitions in Schleife zu erstellen. Oder verwenden Sie einfach 'UniformGrid Columns =" ​​16 "Zeilen =" 16 "' anstelle von 'Grid' – ASh

+3

Warum verwenden Sie nicht FsXAML + FSharp.ViewModule? –

Antwort

3

Wie ich schon sagte im Kommentar einfacher zu FsXAML + FSharp.ViewModule.

ich nicht ganz verstehen, was Sie wollen, so ein einfaches Beispiel:

<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> 

Sie können Items statt List-Box verwenden, aber es wird einige Zeit dauern, =)

.fs Konfiguration :

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

Somit y Sie können mit Ihrem Objekt in der Funktion click alles machen.

P.S. Ich habe mir erlaubt, ein wenig Ihren ToggleButton-Stil zu ändern, um das Bild klarer zu machen.

0

Ich bin kein großer Fan von 3rd-Party-Lösungen als Antwort. Wenn ich Ihre Frage verstehe, versuchen Sie, auf ein Element zuzugreifen, das in Ihrem XAML angegeben wurde. Dies kann leicht durch das Umwandeln Ihrer XAML in einen String-Typ dann getan werden:

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 

Ihr Fall etwas anders sein könnte, aber das ist die allgemeine Idee.