I'd like to use the built-in Silverlight 4.0 field validation on the following code, and am having trouble getting it to work.
MyForm.fs:
// imports, etc
type MyForm() as this =
inherit UriCanvasControl("/Project;component/MyForm.xaml", "Enter Stuff")
[<DefaultValue>]
val mutable myTextBox: TextBox
do
Application.LoadComponent(this, base.uri)
this.myTextBox <- this?myTextBox
// other stuff
MyForm.xaml:
// ...
<TextBox Name="myTextBox" Text="{Binding Path=myTextBox,Mode=TwoWay,ValidatesOnExceptions=True,NotifyOnValidationError=True}" TextChanged="Duration_Changed" Grid.Column="0" Margin="0,0,2,0"></TextBox>
// ...
I've tried putting annotations above the myTextBox field in the .fs file, but the compiler complained about that (annotations like: [Required(ErrorMessage="enter something!")]).
Any ideas? Thanks.
I think you'll have better luck with a property, e.g.
type Yadda() = ...
let mutable backingField : TextBox = null
[<RequiredOrWhatever(blah)>]
member this.TheProperty with get() = backingField
and set(x) = backingField <- x
but I don't know Silverlight details well enough to verify it right now.
Related
I am "modernizing" some 15-month-old code to take advantage of
Kotlin Extension View Binding (migrating away from the deprecated
Kotlin Android Extensions compiler plugin).
The issue I'm having is related to the practice of using vars of
type Array<ConstraintLayout> throughout my code. This is
exemplified by charKeys throughout this posting.
I'm using nested includes within the XML.
I am struggling to figure the correct new syntax or approach. I
cannot get this code to compile yet.
NOTE: All Kotlin & XML has been reduced to relevant sections only.
First, the "old" way - which is working perfectly.
PuzzleFragment.kt
import kotlinx.android.synthetic.main.fragment_puzzle.*
import kotlinx.android.synthetic.main.item_keyboard.*
import kotlinx.android.synthetic.main.item_keyboard.view.*
import kotlinx.android.synthetic.main.item_kkey.view.*
:
class PuzzleFragment : Fragment() {
lateinit var charKeys: Array<ConstraintLayout>
charKeys = arrayOf(
kbd_char_0,
kbd_char_1
:
)
fragment_puzzle.xml
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/puzzle_fragment"
>
<include layout="#layout/item_keyboard" />
</androidx.constraintlayout.widget.ConstraintLayout>
item_keyboard.xml
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/keyboard"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/kbd_char_0"
>
<include layout="#layout/item_kkey" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/kbd_char_1"
>
<include layout="#layout/item_kkey" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
item_kkey.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android"
>
<TextView
android:id="#+id/kkey"
/>
</merge>
Again, all of that is (was) working (using Kotlin Android
Extensions). And this code allows (allowed) me to do things
like:
PuzzleFragment.kt
for (x in 0 until someNumber) {
val shape = charKeys[x].background as GradientDrawable
shape.setStroke(...)
charKeys[x].kkey.setTextColor(...)
for (key in charKeys)
key.isEnabled = false
for ((kx, key) in charKeys.withIndex())
key.elevation = ... //using kx
for (cx in 0 until maxGuessLength)
makeKeyRed(charKeys[cx], true)
private fun makeKeyRed(key: ConstraintLayout, doRed: Boolean) {
when {
doRed -> key.kkey.setTextColor(...)
key.kkey.text != "#" -> key.kkey.setTextColor(...)
else -> key.kkey.setTextColor(...)
}
}
So - that's the old way. Everything's cool. Now I'm converting this code. I have:
PuzzleFragment.kt
import com.zazzem.thats.databinding.FragmentPuzzleBinding
class PuzzleFragment : Fragment(R.layout.fragment_puzzle) {
private var _binding: FragmentPuzzleBinding? = null
private val binding get() = _binding!!
The following code seems ok (no "highlighted" errors in the IDE):
lateinit var charKeys: Array<ConstraintLayout>
charKeys = arrayOf(
binding.item_keyboard.kbd_char_0,
binding.item_keyboard.kbd_char_1,
:
)
for (x in 0 until someNumber) {
val shape = charKeys[x].background as GradientDrawable
shape.setStroke(...)
But it "falls apart" with the next line of code:
charKeys[x].kkey.setTextColor(...)
It doesn't like "kkey" here ("Unresolved reference"). And, in fact,
Basic code completion (Ctrl + Space) shows none of the widgets which
are part of item_kkey.
I don't know if I'm simply missing something obvious? Or if this
whole "array of 'vaguely-typed' ConstraintLayouts" approach isn't
valid (any longer)? Or if it's something in between?
Your .kkey is another use of a synthetic property, so you can't do that without Kotlin Android Extensions. The synthetic property is under the hood using findViewById on whatever ConstraintLayout you call it on to find the child view.
With view binding, no such properties of Views exist. But the generated class does have properties for include blocks. These properties return the generated binding classes of the layout files that you're including. However, you have not used android:id elements on your include elements, so they won't be accessible via your binding class.
Even if you did, that wouldn't be compatible with your ConstraintLayout Array strategy here.
I suggest for ease of converting your code, you create a direct replacement for the synthetic property, like this:
val ConstraintLayout.kkey: TextView get() = findViewById(R.id.kkey)
One downside to this compared to the synthetic property is that it has to find the view every time you use it, which is slightly slower than synthetic properties when they are repeatedly used. But I don't think it will be very significant here, since this particular view is always the sole child of the layout you are searching in.
Note, this strategy has the same weakness that they deprecated Android Extensions for. It's not safe to call this on any arbitrary ConstraintLayout.
I cannot get a ViewModelViewHost to work at design time in Visual Studio. Is this by design or have I set something up wrong?
In my view's ctor I have:
Locator.CurrentMutable.InitializeSplat();
Locator.CurrentMutable.InitializeReactiveUI();
Locator.CurrentMutable.Register(() => new SessionView(), typeof(IViewFor<SessionViewModel>));
In my view's XAML I have:
<d:DesignProperties.DataContext>
<local:MainWindowViewModel>
<local:MainWindowViewModel.ChildViewModel>
<local:SessionViewModel/>
</local:MainWindowViewModel.ChildViewModel>
</local:MainWindowViewModel>
</d:DesignProperties.DataContext>
<reactiveUi:ViewModelViewHost ViewModel="{Binding ChildViewModel}"/>
SessionView is an IViewFor<SessionViewModel>.
There is a comment here in the ViewModelViewHost ctor indicating that in design mode it will return before trying to create a view. But it seems strange that InUnitTestRunner should return true if there is a seperate InDesignMode property for that purpose.
// NB: InUnitTestRunner also returns true in Design Mode
if (ModeDetector.InUnitTestRunner()) {
ViewContractObservable = Observable.Never<string>();
return;
}
Bit of a late reply our apologies.
The answer is this is deliberate.
We have stuff that will break the XAML designer if ReactiveUI's code is allowed to initialize in the constructor. Hence why we don't run the values.
This is driving me nuts. I am looking here at Microsoft's function examples.
I can't see what I am doing wrong.
So, I am trying to use F# with WPF, I found a working online project template for it. Here it is .I was about to get started. Unfortunately, the designer does not work for generating events in the F# code by double clicking an element like it does in C#. But oh well, I can set what the Click does anyway. I decided I would do it all manually.
Here is my flawed attempt:
module MainApp
open System
open System.Windows
open System.Windows.Controls
open FSharpx
let mutable doc = ""
type MainWindow = XAML<"MainWindow.xaml">
let loadWindow() =
let window = MainWindow()
// Your awesome code code here and you have strongly typed access to the XAML via "window"
window.Root
let make (sender:Object, e:RoutedEventArgs) =
doc<- doc +"<?xml version=\"1.0\" standalone=\"no\"?>"
0
[<STAThread>]
(new Application()).Run(loadWindow()) |> ignore
In any event, It does not like the line with let make on it. It gives me this error:
Block following this 'let' is unfinished. Expect an expression
And yet, clearly I read on the MSDN
The compiler uses the final expression in a function body to determine the return value and type
So, it has a return, how is it an unfinished expression?
You are supposed to return window.Root from loadWindow. Right now the last bit of code inside loadWindow is the declaration of a make function, which is not valid. Remember, indentation determines scope in F#.
If you wanted to add a new function make, but leave the body of loadWindow basically empty, you need to align the indentation properly:
let loadWindow() =
let window = MainWindow()
// Your awesome code code here and you have strongly typed access to the XAML via "window"
window.Root
let make (sender:Object, e:RoutedEventArgs) =
doc<- doc +"<?xml version=\"1.0\" standalone=\"no\"?>"
0
XAML
<ComboBox Height="23.338" x:Name="cboCustom" />
Code which runs when the combobox should be populate
cboCustom.Items.Clear()
For x As Integer = 0 To (_collection.count - 1)
cboCustom.Items.Add(_collection.node(x))
Next
The _collection.node() is class with several properties
And I would like to set the displayMembervalue of the WPF combobo to it's description property.
I've tried
cboCustom.DisplayMemberPath = "myitem.description"
and
cboCustom.DisplayMemberPath = "description"
With no luck.
cboCustom.SeletedItem.description (on select action) does however give me the expected value.
Just use "Description" on its own (assuming that's the name of the property you want to display). Also, make sure the casing is correct.
I'm trying to write an automated test harness using WPF and F#. I'd like to display aggregate test results as rows in a multi-column list. Ideally, I would like to present rich content in each cell, e.g. highlighting issues with specific tests using color or making details available via tooltips. However, I cannot figure out how to put anything richer than a plain string into a multi-column ListView.
Furthermore, I'm not a fan of XAML or data binding and prefer to write vanilla F# code. The simplest program I have been able to write that displays a multi-column WPF ListView is:
open System.Windows
let gridView = Controls.GridView()
let listView = Controls.ListView(View=gridView)
type SystemParams = { Name: string; Value: obj }
[<System.STAThread>]
do
let column header binding =
let binding = Data.Binding binding
Controls.GridViewColumn(Header=header, DisplayMemberBinding=binding)
for header, binding in ["Name", "Name"; "Value", "Value"] do
column header binding
|> gridView.Columns.Add
for prop in typeof<SystemParameters>.GetProperties() do
if prop.PropertyType <> typeof<ResourceKey> then
{ Name = prop.Name; Value = prop.GetValue(null, null) }
|> listView.Items.Add
|> ignore
Application().Run(Window(Content=listView)) |> ignore
Although this works, I don't like the way it requires the field names to be duplicated both in the type definition and as strings that are fed to WPF which presumably then uses reflection to resolve them at run-time (yuk!). Ideally, I would like to Add an obj array giving the WPF controls for each cell.
Is ListView capable of this? If so, how do you write a function that accepts a 2D array of controls and returns a ListView that visualizes them?
If not, I will probably use a Grid instead. I have tried DataGrid before and it is just a world of pain in comparison...
EDIT:
Thanks to the answers below, I have been able to come up with a solution. The multiColumnList function in the program below creates list of controls with the given headers and content with selectable rows:
open System.Windows
let multiColumnList columns contents onSelection =
let gridView = Controls.GridView()
let list = Controls.ListView(View=gridView)
let column index header =
let binding = Data.Binding(sprintf "[%d]" index)
Controls.GridViewColumn(Header=header, DisplayMemberBinding=binding)
|> gridView.Columns.Add
Seq.iteri column columns
list.ItemsSource <-
[|for row in contents ->
[|for elt in row ->
box elt|]|]
list.SelectionChanged.Add onSelection
list
[<System.STAThread>]
do
let columns = ["Name"; "Value"]
let contents =
[ for prop in typeof<SystemParameters>.GetProperties() do
if prop.PropertyType <> typeof<ResourceKey> then
yield [box prop.Name; prop.GetValue(null, null)] ]
Application().Run(Window(Content=multiColumnList columns contents ignore))
|> ignore
Yes, it possible but it's a little tricky, but once you've mastered the approach it's quite flexible. WFP has a flexible templating system that this available both though code and XAML, except there are far less examples of how to do this in code.
It basically involves working how to to use the FrameworkElementFactory to override the list box default template and show the UI elements you want. Then using the Binding class to specify how the controls should be bound to the data.
I wrote a twitter client in WPF and F# and I use this approach to display the columns of tweets in list boxes. Take a look at how the createTweetContainerTemplate function works.
https://github.com/robertpi/TwitMemento/blob/master/Strangelights.TwitMemento.WPF/View.fs
Then again unless you really need a high level of control over how each row in the list box should be laid out, it maybe simpler to use a datagrid.
Your specific problem of duplicating the field names could be avoided by using an index based binding, instead of member name. See the changes to your example here:
open System
open System.Windows
let gridView = Controls.GridView()
let listView = Controls.ListView(View=gridView)
[<System.STAThread>]
do
let column index header =
let binding = Data.Binding(sprintf "[%d]" index)
Controls.GridViewColumn(Header=header, DisplayMemberBinding=binding)
["Name"; "Value"]
|> List.mapi column
|> List.iter gridView.Columns.Add
for prop in typeof<SystemParameters>.GetProperties() do
if prop.PropertyType <> typeof<ResourceKey> then
([| prop.Name; prop.GetValue(null, null) |] : Object array)
|> listView.Items.Add
|> ignore
Application().Run(Window(Content=listView)) |> ignore
Regarding giving the ListView a sequence of sequences of controls, that is somewhat lower level than ListView is intended to be used. ListView and DataGrid both assume that you have some roughly homogeneous collection of objects that you want show (generally as rows) and some idea of what information you want to show about those objects (the column definitions). Both controls will help in that situation, although I do agree that their general assumption that you want to use reflection over the members of a type can be annoying.
If you want to be able to specify a grid of any controls, then as you mention the Grid panel layout is probably more suitable.
I made a simple combinator library to build WPF UI thru code, I use this pattern in my pit project for creating HTML elements.
namespace FSharp.WPF
open System
open System.Windows
open System.Windows.Controls
[<AutoOpen>]
module Combinator =
type XDef =
| Attr of string * obj
| Tag of Type * XDef list
//| Element of FrameworkElement
[<AutoOpen>]
module Operators =
let (#=) (p:string) (v:obj) : XDef = Attr(p,v)
module internal Helpers =
let createEl (ty:Type) = new FrameworkElementFactory(ty)
let tag name attr = Tag(name,attr)
//let el dom = Element(dom)
let rec build (tag:XDef) =
match tag with
| Tag(ty,defs) ->
let attrs = defs |> List.choose(fun t -> match t with | Attr(k,v) -> Some(k,v) | _ -> None)
let tags = defs |> List.choose(fun t -> match t with | Tag(k,v) -> Some(t) | _ -> None)
/// create the element and set attributes
let el = Helpers.createEl(ty)
let rec setProps (d:(string*obj) list) =
match d with
| [] -> ()
| (p,v) :: t ->
let dp = System.ComponentModel.DependencyPropertyDescriptor.FromName(p, el.Type,el.Type)
el.SetValue(dp.DependencyProperty,v)
setProps attrs
let rec gen (d:XDef list) =
match d with
| [] -> ()
| h::t ->
let childEl = build(h)
el.AppendChild(childEl)
gen(t)
gen tags
el
//| Element(el) -> el
| Attr(key,value) -> failwith "Unrecognized sequence"
let make xdef =
let fEl = build xdef
let contentEl = new ContentControl()
contentEl.ContentTemplate <- new DataTemplate(VisualTree=fEl)
contentEl :> FrameworkElement
Its very low profile now, just create objects, but it could be extended to do much more with databinding and other things etc., and a bit of type checking should find errors in object creation.
Usage:
module Test =
open System.Windows.Controls
let create() =
tag typeof<System.Windows.Controls.Button> ["Content"#="Hello World"]
|> Combinator.make
let create2() =
tag typeof<StackPanel> [
tag typeof<Button> ["Content"#="Button 1"]
tag typeof<Button> ["Content"#="Button 2"]
]
|> Combinator.make
[<STAThread>]
[<EntryPoint>]
let main(_) =
let el = Test.create2() // Test.create1()
let window = new Window(Content = el, Height = 600.0, Width = 800.0, Title = "WpfApplication1")
let app = new Application()
app.Run(window)
As you see, nesting elements means Panel elements, but there could some extra leverage that allows the type to identify panel elements or content elements. But you get the idea, this could be useful. What do you think?
-Fahad