Home

Maintain a form across query executions

I'm doing a bit of REPLing around a forms based component, but I'd like to maintain the form between query executions.
I've tried

open System.Windows.Forms
let f = Util.Cache(fun _ -> new Form())
f |> Dump //cannot access a disposed object on 2nd query run

The form shows on first query execution no problem.
But it is killed when the query is run a second time and the form becomes disposed.
(btw, my always use fresh processes setting is false)

Can it be done ?

Comments

  • LINQPad restarts the application and message loop on each query execution, which closes all forms. I can't think of any way around this.

  • Dang, I can use a Jupyter notebook to do it, but was hoping to stay in LP.
    If your subconscious throws you a possibility, do post and I'll try it.

  • Can you elaborate the scenario?

  • I'm doing a bit of work with CefSharp and I use the form browser during development efforts to help visualise things in the browser.
    I'm working on a site that requires me to log in to get at the parts of interest, and whilst I can automate the login process, I wanted to avoid having to relogin each time I REPL my query.
    So for all intents I wanted to consider the form/browser an expensive thing to create, and being able to Util.Cache it between runs would be epic.

  • You could try using WPF. It lets you create an application with a message loop that's independent of global state. It will need to run on its own thread, because app.Run blocks:

    void Main()
    {
        // Window must be of type Window (not MyWindow) so that we can cache it.
        // We cannot usefully cache MyWindow because it's recompiled whenever
        // the query changes, changing its type.
        var window = GetWindow (() => new MyWindow());  
    
        // LINQPad's Uncapsulate() does the job of casting to dynamic, but is nicer to use.
        window.Dispatcher.Invoke (() => window.Uncapsulate().txtTest.Text.Dump());
    }
    
    class MyWindow : Window
    {
        public readonly Label lblTest = new Label
        {
            Content = "Test:"
        };
        public readonly TextBox txtTest = new TextBox
        {
            VerticalContentAlignment = VerticalAlignment.Center
        };
    
        public MyWindow()
        {
            Title = "Test";
            Padding = new Thickness(5);
    
            var dock = new DockPanel();
            dock.Children.Add (lblTest);
            dock.Children.Add (txtTest);
            lblTest.SetValue (DockPanel.DockProperty, Dock.Left);
    
            var content = new StackPanel();
            content.Children.Add (dock);        
            Content = content;
        }
    }
    
    Window GetWindow (Func<Window> windowCreator) =>
        Util.Cache (() => CreateWindow (windowCreator));
    
    Window CreateWindow (Func<Window> windowCreator)
    {
        var windowSource = new TaskCompletionSource<Window>();
        var thread = new Thread (CreateWindowCore);
        thread.SetApartmentState (ApartmentState.STA);
        thread.Start();
        return windowSource.Task.Result;
    
        void CreateWindowCore()
        {
            // Create a new application so we can run a message loop
            // that's independent of the default dispatcher.
            var app = new Application();
            var window = windowCreator();
            windowSource.SetResult (window);
            app.Run (window);
        }
    }
    
  • Nice. Will give it a spin and see how I go. Thanks.

  • For anyone landing here. Joe's suggestion of WPF works great, however the WPF CefSharp browser has performance issues on high DPI machines. So the workaround is to use the CefSharp forms based browser and host it in a WPF app via a WindowsFormsHost control. Make sure the initialisation of CefSharp is done on the WPF thread not on the linqpad thread as the form will die between between restarts.

    Now things are epic. Damn linqpad is good...

Sign In or Register to comment.