'How to approach writing a WPF Custom Control in F#?

I am trying to understand how a wpf custom control could be written in F#.

As an example, I have the following C# code for a drag and drop on a canvas (in C#). It inherits from ListBox. I'm not looking for anybody to rewrite this. But I'm at a loss as to how it would be implemented in Elmish.wpf since there is no xaml to deal with. (I believe a Custom Control does not have a XAML interface).

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Stargate.XI.Client.Views.CustomControls
{
    public delegate void DropCompletedEventHandler(object sender, DropCompletedEventArgs e);


    // To add a custom DropCompletedEvent to an ItemsControl, I would either have to have an attached property, as in
    // https://stackoverflow.com/questions/15134514/attached-behavior-handling-an-attached-event-in-wpf 
    // or subclass an ItemsControl as below. Creating a simple custom control, like here, seems cleaner.
    // Note: ItemsControl can't select items, only present collections. Only a Selector or one of it's descendants can select items
    //       Hence, only the ListBox or its derivative,ListView, have Selector's.
    public class ChartCanvas : ListBox
    {
        public event EventHandler PlayMusicEvent;
        public event EventHandler PauseMusicEvent;
        public event EventHandler StopMusicEvent;
        public event EventHandler DisposeMusicEvent;
        public event EventHandler DisposePosterEvent;

        #region DropCompletedEvent
        // Create a custom routed event by first registering a RoutedEventID
        // This event uses the bubbling routing strategy
        public static readonly RoutedEvent DropCompletedEvent = EventManager.RegisterRoutedEvent(
            "DropCompleted", RoutingStrategy.Bubble, typeof(DropCompletedEventHandler), typeof(ChartCanvas));

        // Provide CLR accessors for the event. The RoutedEventHandler, e.g., "DropCompleted" is used in the xaml declaration for the ImageCanvas.
        public event DropCompletedEventHandler DropCompleted
        {
            add { AddHandler(DropCompletedEvent, value); }
            remove { RemoveHandler(DropCompletedEvent, value); }
        }

        // This method raises the DropCompleted event
        public void RaiseDropCompletedEvent(object datatype)
        {
            RaiseEvent(new DropCompletedEventArgs(DropCompletedEvent, datatype));
        }
        #endregion

        public ChartCanvas()
        {
            AllowDrop = true;
            DragEnter += IC_DragEnter;
            Drop += IC_Drop;
            DragOver += IC_DragOver;
            DragLeave += IC_DragLeave;
        }

        private void IC_DragLeave(object sender, DragEventArgs e)
        {
            e.Handled = true;
        }

        private void IC_DragOver(object sender, DragEventArgs e)
        {
            e.Handled = true;
        }

        private void IC_Drop(object sender, DragEventArgs e)
        {
            var data = e.Data.GetData(DataFormats.Text);
            var dragSource = e.Data.GetData("DragSource");

            RaiseDropCompletedEvent(data);
        }

        private void IC_DragEnter(object sender, DragEventArgs e)
        {
            e.Handled = true;
        }

        #region PlayMovie
        private ICommand _playMovie;
        public ICommand PlayMovieCommand
        {
            get
            {
                if (_playMovie == null)
                {
                    _playMovie = new RelayCommand(
                        p => true,
                        p => this.PlayMovie());
                }
                return _playMovie;
            }
        }

        private void PlayMovie()
        {
            PlayMusicEvent?.Invoke(this, EventArgs.Empty);
        }
        #endregion

        #region PauseMovie
        private ICommand _pauseMovie;
        public ICommand PauseMovieCommand
        {
            get
            {
                if (_pauseMovie == null)
                {
                    _pauseMovie = new RelayCommand(
                        p => true,
                        p => this.PauseMovie());
                }
                return _pauseMovie;
            }
        }

        private void PauseMovie()
        {
            PauseMusicEvent?.Invoke(this, EventArgs.Empty);
        }
        #endregion

        #region StopMovie
        private ICommand _stopMovie;
        public ICommand StopMovieCommand
        {
            get
            {
                if (_stopMovie == null)
                {
                    _stopMovie = new RelayCommand(
                        p => true,
                        p => this.StopMovie());
                }
                return _stopMovie;
            }
        }

        private void StopMovie()
        {
            StopMusicEvent?.Invoke(this, EventArgs.Empty);
        }
        #endregion

        public bool Dispose
        {
            get { return (bool)GetValue(DisposeProperty); }
            set { SetValue(DisposeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Dispose.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DisposeProperty =
            DependencyProperty.Register("Dispose", typeof(bool), typeof(ChartCanvas), new PropertyMetadata(false,
                (s,e) =>
                {
                    ChartCanvas chartcanvas = s as ChartCanvas;
                    chartcanvas.DisposeMusicEvent?.Invoke(chartcanvas, EventArgs.Empty);
                    chartcanvas.DisposePosterEvent?.Invoke(chartcanvas, EventArgs.Empty);
                }              
                ));

    }
}

Any suggestions to this newbie as to how to approach this would be much appreciated.

TIA



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source