4

I'm trying to create a simple task scheduler using F# and WPF. It's basically just a list of tasks where every task has a 'Delete' button. Handling button clicks outside of the list is not a problem -- this can be handled with a regular command. However handling a button click in the list item is not straightforward. I tried using RelayCommand described here with binding routed to parent, but the sender object is always null (I expected it to be the task object from the collection). Also tried attaching a property as recommended here, but couldn't make it work.

How do I assign an event handler that obtains the task object with clicked Delete button?

Here is the App.fs:

namespace ToDoApp

open System
open System.Windows
open System.Collections.ObjectModel
open System.Windows.Input
open FSharp.ViewModule
open FSharp.ViewModule.Validation
open FsXaml

type App = XAML<"App.xaml">
type MainView = XAML<"MainWindow.xaml">

type Task(str) = 
    member x.Description with get() = str

type MainViewModel() as self = 
    inherit ViewModelBase()  

    let tasks = new ObservableCollection<Task>()

    let addTaskCommand() =
        let descr = sprintf "Do something at %A" (DateTime.Now.AddMinutes(30.0))
        tasks.Add <| new Task(descr)

    member this.Tasks with get() = tasks
    member this.AddTask = this.Factory.CommandSync addTaskCommand

module main =
    [<STAThread>]
    [<EntryPoint>]
    let main argv =
        App().Run()

MainWindow.xaml:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ToDoApp;assembly=ToDoApp"
    xmlns:fsxaml="http://github.com/fsprojects/FsXaml"
    Title="Simple ToDo app" Height="200" Width="400">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Button Name="newJobButton" Command="{Binding AddTask}" Width="100" Height="32" Margin="5, 5, 5, 5" HorizontalAlignment="Left">New task</Button>
        <ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
            <ListBox Name="lstBox" ItemsSource="{Binding Tasks}" >
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="80" />
                            </Grid.ColumnDefinitions>
                            <Label Grid.Column="0" Content="{Binding Description}" Margin="5 5 0 0"/>
                            <!-- OnClick ??? -->
                            <Button Grid.Column="1">Delete</Button>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </ScrollViewer>
    </Grid>
</Window>

App.xaml is trivial, so I'm not showing it here.

Community
  • 1
  • 1
Dmitry G.
  • 584
  • 5
  • 13

1 Answers1

4

It's better to stick to Commands:

ViewModel

member __.DelTask = 
    __.Factory.CommandSyncParam 
        (fun task -> tasks.Remove task |> ignore)

XAML

<Button Grid.Column="1" 
        Command="{Binding DataContext.DelTask, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" 
        CommandParameter="{Binding}"
        >Delete</Button>

Using event handlers in XAML results in spaghetti code which is harder to test and maintain (i.e. separation of concerns, handling business logic problems separately from UI problems).

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Funk
  • 10,976
  • 1
  • 17
  • 33