1

In order to select all text in a textbox with a click in a WPF app as well as in an Elmish.WPF app, I can use this C#/XAML code:

file SettingsPage.xaml:

<TextBox ....some code... PreviewMouseLeftButtonDown="SelectAllText"> </TextBox>

file Settings.xaml.cs (code behind):

 public partial class Settings : UserControl
    {
        public Settings() => InitializeComponent();

        private void SelectAllText(object sender, MouseButtonEventArgs e)
        {
            TextBox textBox = sender as TextBox;

            if (textBox == null)
            {
                return;
            }

            if (!textBox.IsKeyboardFocusWithin)
            {
                textBox.SelectAll();
                e.Handled = true;
                textBox.Focus();
            }
        }
    }

The code works as expected. But I would like to use F#/Elmish.WPF code instead of C# code. I have tried to create the F# equivalent of the aforementioned code using cmdParam - see below. But it does not work as expected - the text is selected and then it is immediately unselected. Why does it not work and what do I do wrong?

If you need to see more code than shown below, you can find the entire VS solution on GitHub.

file SettingsPage.xaml:

<TextBox  .. some code ... >
    <i:Interaction.Triggers>                    
        <i:EventTrigger EventName="PreviewMouseLeftButtonDown">
            <i:InvokeCommandAction PassEventArgsToCommand="True" Command="{Binding TextBoxClicked}" />
        </i:EventTrigger>                   
    </i:Interaction.Triggers>
</TextBox>

file Settings.fs:

module Settings =    
    //some code          
    type Msg =
        | TextBoxClickedEvent
       //some other messages
    
    let castAs<'T when 'T : null> (o:obj) = 
        match o with
        | :? 'T as res -> res
        | _            -> null //TODO       
    
    let paramTextBoxClickedEvent (p: obj) =  
        let e = castAs<MouseButtonEventArgs>(p)   
        let sender = e.Source
        let textBox = castAs<TextBox>(sender)
        let e =             
            match textBox.IsKeyboardFocusWithin with 
            | true  -> ()
            | false -> 
                      textBox.SelectAll()     
                      e.Handled = true |> ignore
                      textBox.Focus()  |> ignore
        e  
        TextBoxClickedEvent
      
    let update (msg: Msg) (m: Model) : Model * Cmd<Msg> = 
        match msg with   
        | TextBoxClickedEvent  -> m, Cmd.none 
        //some other code

    let bindings(): Binding<Model,Msg> list =
        [ 
           //some code
          "TextBoxClicked" |> Binding.cmdParam paramTextBoxClickedEvent  
        ]      
  • 1
    I think you should stick with the first working solution. Code-behind is not bad, despite what some say. It is a great advantage to keep this kind of GUI-only stuff well separated from the business logic. The second solution takes far too much effort, intrudes in the business domain for no good reason, and its complexity might haunt you later. The first solution can easily be expanded or replaced e.g. to make several or all TextBoxes have the same behavior. Even in cases where business logic needs to be tied in to this kind of feature, I would still use some code-behind if that's easier. – Bent Tranberg May 25 '22 at 10:40
  • Although I have to admit that Bent Tranberg's view on this problem is correct and reasonable, I would appreciate any suggestion why my F# code does not work - it might come in handy in some other situation in some future time. – Miroslav Husťák Jun 07 '22 at 19:07
  • 1
    I suspect you intended `e.Handled <- true` instead of `e.Handled = true |> ignore`. (Will look more into your issue later.) – Bent Tranberg Jun 07 '22 at 20:15
  • 1
    Yeah, that's it. This is the answer. e.Handled <- true instead of e.Handled = true |> ignore. Now it works and it gives me more knowledge of how Binding.cmdParam works. – Miroslav Husťák Jun 07 '22 at 21:46
  • I suggest you post an answer that shows that line before and after the fix. It's perfectly allright to answer your own questions here on SO. – Bent Tranberg Jun 08 '22 at 08:58

1 Answers1

1

What was wrong:

The equivalent of C# code e.Handled = true; is not e.Handled = true in F#, but e.Handled <- true.

Answer:

The relevant function is now: `

  let paramTextBoxClickedEvent (p: obj) = 
        let e = castAs<MouseButtonEventArgs>(p) 
        let sender = e.Source
        let textBox = castAs<TextBox>(sender) 
         
        let e =             
            match textBox.IsKeyboardFocusWithin with 
            | true  -> ()
            | false -> 
                      textBox.SelectAll()     
                      e.Handled <- true
                      textBox.Focus() |> ignore
        e  
        TextBoxClickedEvent

This code now works. It may give you more inside into how Binding.cmdParam works. Anyway, do consider Bent Tranberg's comment (related to C#/code behind and separation of logic) first before using the F#/Elmish.WPF solution.