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!
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
In my small WPF app (F# only) I would like to remember the window size and location after closing. This C# solution suggest to use the projects settings IDictinary for User.config. This looks like the simple approach that I am after but I did not find the project settings in my F# project. Do they exist for F# projects?
I tried this but it does not work: (The save() call as in the C# example is not available.)
let getSetting k def =
if Application.Current.Properties.Contains k
then Application.Current.Properties.Item k
else def
let window = System.Windows.Window()
// is box() the best wax to make floats into obj ?
window.Top <- getSetting "WindowTop" (box 0.0) |> unbox
window.Left <- getSetting "WindowLeft" (box 0.0) |> unbox
window.Height <- getSetting "WindowHeight" (box 800.0) |> unbox
window.Width <- getSetting "WindowWidth" (box 800.0) |> unbox
window.Closing.Add( fun _ ->
Application.Current.Properties.Add("WindowTop",window.Top)
Application.Current.Properties.Add("WindowHeight",window.Height)
Application.Current.Properties.Add("WindowLeft",window.Left)
Application.Current.Properties.Add("WindowWidth",window.Width)
//Application.Current.Properties.Save() // not available!
)
I know I could use a type provider but I would like to keep it simple and without dependencies if possible. Is there a built in way to persist some user values in an F# WPF app?
As #mm8 points out, the approach you mention depends on the Settings.settings file included in the project template of a C# WPF app.
The F# template does not provide such functionality, which is unfortunate given the XAML support is pretty cool. Instead, you could resort to the App.config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
...
<appSettings>
<add key="WindowTop" value="0" />
<add key="WindowLeft" value="0" />
<add key="WindowHeight" value="350" />
<add key="WindowWidth" value="525" />
</appSettings>
</configuration>
If you don't want to depend on FSharp.Configuration.dll you can use the ConfigurationManager (note you still need to add a reference to System.Configuration.dll).
open System.Configuration
type UserSettings = {
WindowTop : float
WindowLeft : float
WindowHeight : float
WindowWidth : float
} with
static member Load() =
let config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
{ // To do: add validation
WindowTop = float config.AppSettings.Settings.["WindowTop"].Value
WindowLeft = float config.AppSettings.Settings.["WindowLeft"].Value
WindowHeight = float config.AppSettings.Settings.["WindowHeight"].Value
WindowWidth = float config.AppSettings.Settings.["WindowWidth"].Value
}
member this.Save() =
let config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
config.AppSettings.Settings.["WindowTop"].Value <- string this.WindowTop
config.AppSettings.Settings.["WindowLeft"].Value <- string this.WindowLeft
config.AppSettings.Settings.["WindowHeight"].Value <- string this.WindowHeight
config.AppSettings.Settings.["WindowWidth"].Value <- string this.WindowWidth
config.Save()
Now you can:
open System.Windows
let window = new Window()
let settings = UserSettings.Load()
window.Top <- settings.WindowTop
window.Left <- settings.WindowLeft
window.Height <- settings.WindowHeight
window.Width <- settings.WindowWidth
window.Closing.Add( fun _ ->
{
WindowTop = window.Top
WindowLeft = window.Left
WindowHeight = window.Height
WindowWidth = window.Width
}.Save() )
Thanks to the answer of Funk I came up with this as the minimal general solution using System.Configuration.dll:
module Config =
open System.Configuration
let private config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
let save key value =
try config.AppSettings.Settings.[key].Value <- value
with _ -> config.AppSettings.Settings.Add(key,value) // in case key does not exist yet
config.Save()
let load key =
try Some config.AppSettings.Settings.[key].Value
with _ -> None
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()
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.