I wrote the following code:
open System.Windows.Forms
open System.Drawing
let f = new Form(Text="Clock", TopMost=true)
f.Show()
type Editor() =
inherit UserControl()
override this.OnMouseDown e =
printfn "Mouse Down"
override this.OnKeyDown e =
match e.KeyCode with
| Keys.W -> printfn "W pressed"
| _ -> printfn "Something else pressed"
let e = new Editor(Dock = DockStyle.Fill)
f.Controls.Add(e)
The problem is that if i press "W" or another random Key i got no message on the console, why ?
For the mouse all is working fine.
The problem is that your Editor component does not have focus. Adding the following solves the problem:
e.Focus()
Related
I am tinkering with using F# scripts and I'm just wanting to draw lines on a blank Windows Form with a simple button click. Hopefully you can see what I'm trying to do here:
open System.Drawing
open System.Windows.Forms
let form = new Form(Width = 400, Height = 400, Text = "draw test")
let panel = new FlowLayoutPanel()
form.Controls.Add(panel)
let paint(e : PaintEventArgs) =
let pen = new Pen(Color.Black);
e.Graphics.DrawLine(pen, new PointF(100.0f, 100.0f), new PointF(200.0f, 200.0f))
let button = new Button()
button.Text <- "Click to draw"
button.AutoSize <- true
button.Click.Add(fun _ -> form.Paint.Add(paint)) // <- does not draw a line on click
panel.Controls.Add(button)
//form.Paint.Add(paint) <- here, if uncommented, it will draw a line when the script is run
form.Show()
If I take the form.Paint.Add(paint) uncomment it above form.Show(), then of course it will draw on the form, but I'm trying to do it with a button click. It's not exactly clear to me how to make this happen in a script like this, and I've been scouring all over for a similar example in F#. Any help would be appreciated.
If you add your Paint event handler before the form is drawn for the first time, then it will draw using that handler.
If you add it after, you need to make sure the form then redraws itself. You could for instance call Refresh or Invalidate on it.
Ex.:
button.Click.Add(fun _ -> form.Paint.Add(paint); form.Invalidate())
Originally an edit, I moved it to the answer section:
Okay, so I was confused about the difference between WPF and Winforms due to the fact that I have seen the terms used together various places... #Asik has added an answer for Winforms, but here I have slapped together a working .fsx script specifically for WPF based on several FSharp Snippets (as well as several Google searches) which can also be compiled if so desired. I'll update this as needed or requested. Also, just to point out, the whole motivation behind this is to be able to quickly test drawing graphics via FSI.
#r #"PresentationCore"
#r #"PresentationFramework"
#r #"WindowsBase"
#r #"System.Xaml"
#r #"UIAutomationTypes"
open System
open System.Windows
open System.Windows.Media
open System.Windows.Shapes
open System.Windows.Controls
let window = Window(Height = 400.0, Width = 400.0)
window.Title <- "Draw test"
let stackPanel = StackPanel()
window.Content <- stackPanel
stackPanel.Orientation <- Orientation.Vertical
let button1 = Button()
button1.Content <- "Click me to draw a blue ellipse"
stackPanel.Children.Add button1
let button2 = Button()
button2.Content <- "Click me to draw a red ellipse"
stackPanel.Children.Add button2
let clearButton = Button()
clearButton.Content <- "Click me to clear the canvas"
stackPanel.Children.Add clearButton
let canvas = Canvas()
canvas.Width <- window.Width
canvas.Height <- window.Height
stackPanel.Children.Add canvas
let buildEllipse height width fill stroke =
let ellipse = Ellipse()
ellipse.Height <- height
ellipse.Width <- width
ellipse.Fill <- fill
ellipse.Stroke <- stroke
ellipse
let ellipse1 = buildEllipse 100.0 200.0 Brushes.Aqua Brushes.Black
Canvas.SetLeft(ellipse1, canvas.Width / 10.0) //messy, will fix at some point!
Canvas.SetTop(ellipse1, canvas.Height / 10.0)
let ellipse2 = buildEllipse 200.0 100.0 Brushes.Red Brushes.DarkViolet
Canvas.SetLeft(ellipse2, canvas.Width / 4.0)
Canvas.SetTop(ellipse2, canvas.Height / 5.0)
let addEllipseToCanvas (canvas:Canvas) (ellipse:Ellipse) =
match canvas.Children with
| c when c.Contains ellipse ->
canvas.Children.Remove ellipse
canvas.Children.Add(ellipse) |> ignore //needs to be removed and readded or the canvas complains
| _ ->
canvas.Children.Add(ellipse) |> ignore
button1.Click.Add(fun _ -> addEllipseToCanvas canvas ellipse1)
button2.Click.Add(fun _ -> addEllipseToCanvas canvas ellipse2)
clearButton.Click.Add(fun _ -> canvas.Children.Clear())
#if INTERACTIVE
window.Show()
#else
[<EntryPoint; STAThread>]
let main argv =
let app = new Application()
app.Run(window)
#endif
Trying to retrieve a DialogResult from a window in a MVVM app, I stumbled on this previous question. After implementing the suggested changes, the sample looks like:
type DialogCloser() =
static let DialogResultProperty =
DependencyProperty.RegisterAttached("DialogResult", typeof<bool>, typeof<DialogCloser>, new PropertyMetadata(DialogResultChanged))
static member GetDialogResult (a:DependencyObject) =
a.GetValue(DialogResultProperty) :?> bool
static member SetDialogResult (a:DependencyObject) (value:string) =
a.SetValue(DialogResultProperty, value)
member this.DialogResultChanged (a:DependencyObject) (e:DependencyPropertyChangedEventArgs) =
let window = a :?> Window
match window with
| null -> failwith "Not a Window"
| _ -> window.DialogResult <- System.Nullable (e.NewValue :?> bool)
Now DialogResultChanged is used before it is declared, which of course doesn't compute in F#.
I can't seem to find a working solution, any help would be appreciated .
I'm not a WPF expert, but in this related C# solution, the DialogResultsChanged method is static. If you define the method as static in F# too, you should be able to reference it before it is declared (using the full name DialogCloser.DialogResultsChanged), so something like the following should do the trick:
type DialogCloser() =
static let DialogResultProperty =
DependencyProperty.RegisterAttached
( "DialogResult", typeof<bool>, typeof<DialogCloser>,
new PropertyMetadata(DialogCloser.DialogResultChanged))
static member GetDialogResult (a:DependencyObject) =
a.GetValue(DialogResultProperty) :?> bool
static member SetDialogResult (a:DependencyObject) (value:string) =
a.SetValue(DialogResultProperty, value)
static member DialogResultChanged
(a:DependencyObject) (e:DependencyPropertyChangedEventArgs) =
let window = a :?> Window
match window with
| null -> failwith "Not a Window"
| _ -> window.DialogResult <- System.Nullable (e.NewValue :?> bool)
I have this really simple program to get RSS-feeds from a website and populate a listbox with the items. Whenever the user selects an item and presses Enter, it should go to a web-page. this is the KeyUp event handler!
rssList.KeyUp
|> Event.filter (fun e -> rssList.SelectedItems.Count > 0)
|> Event.filter (fun (args:Input.KeyEventArgs) -> args.Key = Key.Enter)
|> Event.add -> let feed = unbox<RSSFeed> rssList.SelectItem)
Process.Start(feed.Link) |> ignore)
What I'm getting is the following:
the first time the event triggers, it works fine, the browser open and a page is loaded
the second time it triggers TWO times, so now i get two browser windows opened and the page is loaded in both of them.
the third time I get Three browser . . . You get the idea!
Anybody any idea why this is happening? My goal is (you guessed it) just to open 1 browser window and 1 page PER trigger
There are several compilation errors in your example including a malformed lambda expression, mismatched parentheses, incorrect identitfiers (SelectItem is not a property, I'm assuming you mean SelectedItem not SelectedItems), and incorrect indentation following the let feed binding.
Below is a simplified example that works as you intended. The selected item in the top ListBox is put into the bottom ListBox when the user hits Enter.
open System
open System.Windows
open System.Windows.Controls
open System.Windows.Input
[<EntryPoint>]
[<STAThread>]
let main argv =
let panel = new DockPanel()
let listBox = new ListBox()
for i in [| 1 .. 10 |] do
listBox.Items.Add i |> ignore
DockPanel.SetDock(listBox, Dock.Top)
let listBox2 = new ListBox(Height = Double.NaN)
panel.Children.Add listBox |> ignore
panel.Children.Add listBox2 |> ignore
listBox.KeyUp
|> Event.filter (fun e -> listBox.SelectedItems.Count > 0)
|> Event.filter (fun e -> e.Key = Key.Enter)
|> Event.add (fun e -> let i = unbox<int> listBox.SelectedItem
listBox2.Items.Add(i) |> ignore)
let win = new Window(Content = panel)
let application = new Application()
application.Run(win) |> ignore
0
I got it working properly by implementing the Handled property of the event args with the following:
let doubleClick = new MouseButtonEventHandler(fun sender (args:MouseButtonEventArgs) ->
let listBox = unbox<ListBox> sender
match listBox.SelectedItems.Count > 0 with
| true ->
let listBox = unbox<ListBox> sender
let feed = unbox<RSSFeed> listBox.SelectedItem
Process.Start(feed.Link) |> ignore
args.Handled <- true; ()
| false ->
args.Handled <- true; ())
thanks to everyone who helped me here!
Is the WPF event loop in this answer still a good one for FSI (besides rethrow which is now reraise)? The answer is from 2008 and I'm not sure if there are any "better" implementations around. If not what would one be?
My understanding is that the default implementation is for WinForms.
Yes the default is for Winforms, I do use the WpfEventLoop quite a lot, Code is below,
#I "c:/Program Files/Reference Assemblies/Microsoft/Framework/v3.0"
#I "C:/WINDOWS/Microsoft.NET/Framework/v3.0/WPF/"
#r "PresentationCore.dll"
#r "PresentationFramework.dll"
#r "WindowsBase.dll"
module WPFEventLoop =
open System
open System.Windows
open System.Windows.Threading
open Microsoft.FSharp.Compiler.Interactive
open Microsoft.FSharp.Compiler.Interactive.Settings
type RunDelegate<'b> = delegate of unit -> 'b
let Create() =
let app =
try
// Ensure the current application exists. This may fail, if it already does.
let app = new Application() in
// Create a dummy window to act as the main window for the application.
// Because we're in FSI we never want to clean this up.
new Window() |> ignore;
app
with :? InvalidOperationException -> Application.Current
let disp = app.Dispatcher
let restart = ref false
{ new IEventLoop with
member x.Run() =
app.Run() |> ignore
!restart
member x.Invoke(f) =
try
disp.Invoke(DispatcherPriority.Send,new RunDelegate<_>(fun () -> box(f ()))) |> unbox
with e -> eprintf "\n\n ERROR: %O\n" e; reraise()
member x.ScheduleRestart() = ()
//restart := true;
//app.Shutdown()
}
let Install() = fsi.EventLoop <- Create()
WPFEventLoop.Install()
Test code
open System
open System.Windows
open System.Windows.Controls
let window = new Window(Title = "Simple Test", Width = 800., Height = 600.)
window.Show()
let textBox = new TextBox(Text = "F# is fun")
window.Content <- textBox
Let me know if this helps.
-Fahad
I have a method in a silverlight application. I want to start calling this method when an event occurs (mouse move), and continue to call this method every 1 second until a simple boolean condition changes. Is this possible ? I can't work out how to get the rx to generate multiple 'events' from the single event
Here's a method that I used to do this:
var isChecked = from f in Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
h => new RoutedEventHandler(h),
h => checkBox1.Checked += h,
h => checkBox1.Checked -= h)
where checkBox1.IsChecked == true
select new Unit();
var xs = from click in Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
h => new RoutedEventHandler(h),
h => button1.Click += h,
h => button1.Click -= h)
from ping in Observable.Interval(TimeSpan.FromSeconds(1.0)).TakeUntil(isChecked)
select ping;
_subscription = xs.ObserveOnDispatcher().Subscribe(v => label1.Content = v.ToString());
I created a button to begin the events on click. I created a check box that will stop the events when it is checked. This may not be exactly what you want but the events fire as you asked and you should be able to make modifications to suit from here.