FSI WPF Event Loop - wpf

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

Related

F# WPF script, draw graphics on button click

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

XPSDocumentWriter - Printing Specific Pages to Specific Trays

I'm currently working on a printing application. This app has the requirement that certain pages need to come from specific trays on the printer. Here's the guts of what I've got so far:
foreach (var dto in dispensersToPrint)
{
var documents = FilterDocumentSections(DispenserDocumentsToPrint.RetrieveByDispenserId(dto.DispenserId));
var groupedDocs = documents.GroupBy(t => t.DocumentTypeId);
var queueName = Properties.Settings.Default.PrinterName;
var queue = RawPrinterHelper.GetPrintQueue(queueName);
var seq = new FixedDocumentSequence();
var xpsWriter = PrintQueue.CreateXpsDocumentWriter(queue);
foreach (var docGroup in groupedDocs)
{
var printTicket = queue.DefaultPrintTicket.Clone();
var printTray = MapPrintTray((DocumentSectionType)docGroup.Key);
if (!printTray.IsNullOrEmpty())
{
printTicket = RawPrinterHelper.ModifyPrintTicket(printTicket, "psk:JobInputBin", printTray);
}
var fixedDoc = new FixedDocument();
fixedDoc.PrintTicket = printTicket;
foreach (var doc in docGroup)
{
var pageContent = new PageContent();
var fixedPage = new FixedPage();
var localFileName = string.Empty;
var unzippedFileName = string.Empty;
//copy files locally
localFileName = CopyFileToLocalMachine(doc.FileName);
//unzip file
unzippedFileName = EmfPrintingHelper.UnzipEmfFile(localFileName);
var itemToPrint = new PrintableEmfImage
{
DataContext = new EmfImageViewModel { FileName = unzippedFileName }
};
fixedPage.Children.Add(itemToPrint);
pageContent.Child = fixedPage;
fixedDoc.Pages.Add(pageContent);
}
var docRef = new DocumentReference();
docRef.SetDocument(fixedDoc);
seq.References.Add(docRef);
}
xpsWriter.Write(seq);
}
At a real high level:
For each Dispenser (Work Order) i need to print; i first start by grouping by the DocumentType (i.e. Print type A to tray 1)
I then create a new FixedDocumentSequence
For each DocumentType; I then create a fixed document. I then modify the print ticket to look at the appropriate tray.
I then build each individual page for each document type; and add them to the FixedDocument
Once the building of the FixedDocument is complete; I append it to the DocumentSequence.
I then send the FixedDocumentSequence to the xpsWriter.
But for some reason; these settings aren't being honored. I get all the documents printing out of the same tray.
Here are some of my observations so far:
The modifying of the print ticket does work; I've verified this by sending a modified printTicket into the xpsWriter; but this applies the settings to the entire job; which is a no go for me.
When querying my print capabilities; i noticed that i only have JobInputBin. I don't quite think this means this printer doesn't support the functionality; as multi-tray printing works from a similar WindowsForms app (which uses PageSettings.PaperSource)
Any ideas on what I could try next? Has anyone been successful doing something like this before?
I'll start off by saying, I don't have access to a printer with trays, so I am unfortunately not capable of testing this solution. That said, I'll direct your attention to an MSDN forum post, here, where the original poster was in pursuit of the same tray-per-page behavior.
Based on your posted code, you may have already seen some of what's in this post, judging by your posted code having at least some implementation of ModifyPrintTicket().
In the post, there are several different users, each citing a solution for their specific version of the problem. However, the one that seems most relevant in this case is the solution regarding namespaces not being correctly accounted for in ModifyPrintTicket() (as posted by
Jo0815). I say 'most relevant' because the poster speaks of the print tray being disregarded. They (wittersworld) provide an alternate implementation to correct the issue. In the post on MSDN, the link to the complete source is broken, but can be located here.
The gist is, on ModifyPrintTicket(), they add a namespaceUri parameter, then withing changed this:
if (node != null)
{
node.Attributes["name"].Value = newValue;
}
to this:
if (node != null)
{
if (newValue.StartsWith("ns0000"))
{
// add namespace to xml doc
XmlAttribute namespaceAttribute = xmlDoc.CreateAttribute("xmlns:ns0000");
namespaceAttribute.Value = namespaceUri;
xmlDoc.DocumentElement.Attributes.Append(namespaceAttribute);
}
node.Attributes["name"].Value = newValue;
}
allowing the user to specify the printer-specific namespace used.
I hope this is helpful.

F# Event is being triggered multiple times . . .?

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!

WPF Memory Usage with UserControls & Printing

Here's a snippet of code I have to print a document in WPF. At a high level, I instantiate a UserControl, and then send it to a printer.
var printQueue = GetQueues().Where(t => t.Name == comboBox1.SelectedItem.ToString()).FirstOrDefault();
var defaultPrintTicket = printQueue.DefaultPrintTicket.Clone();
var newTicket = ModifyPrintTicket(defaultPrintTicket, "psk:JobInputBin",
((PrintTrays)listBox1.SelectedItem).ConfigValue);
var xpsWriter = PrintQueue.CreateXpsDocumentWriter(printQueue);
var controlToPrint = new PackingSlip();
var fixedDoc = new FixedDocument();
var pageContent = new PageContent();
var fixedPage = new FixedPage();
fixedPage.Children.Add(controlToPrint);
((System.Windows.Markup.IAddChild)pageContent).AddChild(fixedPage);
fixedDoc.Pages.Add(pageContent);
xpsWriter.Write(fixedDoc, newTicket);
What I'm wondering about is that over time, I'll have created hundreds of the instances of controlToPrint. Am I going to run into memory issues here, or do they get disposed of automatically in some fashion? If they aren't disposed automatically, how would I free up that memory?
They will be disposed of automatically assuming that fixedDoc, fixedPage, pagecontent and controlToPrint go out of scope at the end of the method. If application roots hold onto any reference to any of those objects then you will get a memory leak

Printing a WPF FlowDocument

I'm building a demo app in WPF, which is new to me. I'm currently displaying text in a FlowDocument, and need to print it.
The code I'm using looks like this:
PrintDialog pd = new PrintDialog();
fd.PageHeight = pd.PrintableAreaHeight;
fd.PageWidth = pd.PrintableAreaWidth;
fd.PagePadding = new Thickness(50);
fd.ColumnGap = 0;
fd.ColumnWidth = pd.PrintableAreaWidth;
IDocumentPaginatorSource dps = fd;
pd.PrintDocument(dps.DocumentPaginator, "flow doc");
fd is my FlowDocument, and for now I'm using the default printer instead of allowing the user to specify print options. It works OK, except that after the document prints, the FlowDocument displayed on screen has changed to to use the settings I specified for printing.
I can fix this by manually resetting everything after I print, but is this the best way? Should I make a copy of the FlowDocument before I print it? Or is there another approach I should consider?
yes, make a copy of the FlowDocument before printing it. This is because the pagination and margins will be different. This works for me.
private void DoThePrint(System.Windows.Documents.FlowDocument document)
{
// Clone the source document's content into a new FlowDocument.
// This is because the pagination for the printer needs to be
// done differently than the pagination for the displayed page.
// We print the copy, rather that the original FlowDocument.
System.IO.MemoryStream s = new System.IO.MemoryStream();
TextRange source = new TextRange(document.ContentStart, document.ContentEnd);
source.Save(s, DataFormats.Xaml);
FlowDocument copy = new FlowDocument();
TextRange dest = new TextRange(copy.ContentStart, copy.ContentEnd);
dest.Load(s, DataFormats.Xaml);
// Create a XpsDocumentWriter object, implicitly opening a Windows common print dialog,
// and allowing the user to select a printer.
// get information about the dimensions of the seleted printer+media.
System.Printing.PrintDocumentImageableArea ia = null;
System.Windows.Xps.XpsDocumentWriter docWriter = System.Printing.PrintQueue.CreateXpsDocumentWriter(ref ia);
if (docWriter != null && ia != null)
{
DocumentPaginator paginator = ((IDocumentPaginatorSource)copy).DocumentPaginator;
// Change the PageSize and PagePadding for the document to match the CanvasSize for the printer device.
paginator.PageSize = new Size(ia.MediaSizeWidth, ia.MediaSizeHeight);
Thickness t = new Thickness(72); // copy.PagePadding;
copy.PagePadding = new Thickness(
Math.Max(ia.OriginWidth, t.Left),
Math.Max(ia.OriginHeight, t.Top),
Math.Max(ia.MediaSizeWidth - (ia.OriginWidth + ia.ExtentWidth), t.Right),
Math.Max(ia.MediaSizeHeight - (ia.OriginHeight + ia.ExtentHeight), t.Bottom));
copy.ColumnWidth = double.PositiveInfinity;
//copy.PageWidth = 528; // allow the page to be the natural with of the output device
// Send content to the printer.
docWriter.Write(paginator);
}
}
You can use the code from the URL below, it wraps the flow document in a fixed document and prints that, the big advantage is that you can use it to add margin, headers and footers.
https://web.archive.org/web/20150502085246/http://blogs.msdn.com:80/b/fyuan/archive/2007/03/10/convert-xaml-flow-document-to-xps-with-style-multiple-page-page-size-header-margin.aspx
The following works with both text and non-text visuals:
//Clone the source document
var str = XamlWriter.Save(FlowDoc);
var stringReader = new System.IO.StringReader(str);
var xmlReader = XmlReader.Create(stringReader);
var CloneDoc = XamlReader.Load(xmlReader) as FlowDocument;
//Now print using PrintDialog
var pd = new PrintDialog();
if (pd.ShowDialog().Value)
{
CloneDoc.PageHeight = pd.PrintableAreaHeight;
CloneDoc.PageWidth = pd.PrintableAreaWidth;
IDocumentPaginatorSource idocument = CloneDoc as IDocumentPaginatorSource;
pd.PrintDocument(idocument.DocumentPaginator, "Printing FlowDocument");
}
I am also generating a WPF report off a Flow document, but I am purposely using the flow document as a print preview screen. I there for want the margins to be the same. You can read about how I did this here.
In your scenario I'm thinking why not just make a copy of your settings, instead of the entire flow document. You can then re-apply the settings if you wish to return the document back to it's original state.

Resources