Friday, February 29, 2008

Learning WPF with F# - TreeView and ListView

Working through Chapter 16 of Petzold's bookApplications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation.

The most interesting effort in working through these examples is converting the repeated if statements in the implementation of Convert method in MetadataToFlags to use zip,filter,map and combine. The column definitions in DependencyPropertyListView has also been refactored to define all the definitions in a list and iterate through the list to create each GridViewColumn.


ManuallyPopulateTreeView

#light
#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0"
#r @"WindowsBase.dll"
#r @"PresentationCore.dll"
#r @"PresentationFramework.dll"

open System
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media
//
// From Chapter 16 - ManuallyPopulateTreeView
//
//----------------------------------------------------------

let addSubItems items (branch:TreeViewItem) = items |> Seq.iter (fun item ->
item |> branch.Items.Add |> ignore)

let tree = new TreeView()

tree.Items.Add
(let animalBranch = new TreeViewItem(Header="Animal")

animalBranch.Items.Add
(let branch = new TreeViewItem(Header="Dog")
let dogs = ["Poodle";"Irish Setter";"German Shepherd"]
addSubItems dogs branch
branch) |>ignore

animalBranch.Items.Add
(let branch = new TreeViewItem(Header="Cat")
branch.Items.Add(new TreeViewItem(Header="Alley Cat")) |>ignore
branch.Items.Add(new Button(Content="Noodles")) |>ignore
branch.Items.Add("Siamese") |>ignore
branch) |> ignore

animalBranch.Items.Add
(let branch = new TreeViewItem(Header="Primate")
let primates = ["Chimpanzee";"Bonobo";"Human"]
addSubItems primates branch
branch) |> ignore
animalBranch) |> ignore

tree.Items.Add
(let branch = new TreeViewItem(Header="Mineral")
let minerals = ["Calcium";"Zinc";"Iron"]
addSubItems minerals branch
branch) |> ignore

tree.Items.Add
(let branch = new TreeViewItem(Header="Vegetable")
let vegetables = ["Carrot";"Asparagus";"Broccoli"]
addSubItems vegetables branch
branch) |> ignore

let window = new Window(Title="Manually Populate TreeView",
Content=tree)


#if COMPILED
[<STAThread()>]
do
let app = Application() in
app.Run(window) |> ignore
#endif

RecurseDirectoriesInefficiently

#light
#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0"
#r @"WindowsBase.dll"
#r @"PresentationCore.dll"
#r @"PresentationFramework.dll"

open System
open System.IO
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media
//
// From Chapter 16 - RecurseDirectoriesInefficiently
//
//----------------------------------------------------------

let rec GetSubDir (item:TreeViewItem) =
let dir = item.Tag :?> DirectoryInfo
try
dir.GetDirectories() |> Seq.iter (fun subdir ->
let subitem = new TreeViewItem(Header=subdir.Name,Tag=subdir)
subitem |> item.Items.Add |> ignore
// recursively obtain subdirectories
subitem |> GetSubDir)
with _ -> ()

let tree = new TreeView()

tree.Items.Add
(let item = new TreeViewItem(Header = Path.GetPathRoot(Environment.SystemDirectory))
item.Tag <- new DirectoryInfo(item.Header :?> string)
GetSubDir item
item)|>ignore


let window = new Window(Title="Recurse Directories Inefficiently",
Content=tree)


#if COMPILED
[<STAThread()>]
do
let app = Application() in
app.Run(window) |> ignore
#endif

RecurseDirectoriesIncrementally

#light
#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0"
#r @"WindowsBase.dll"
#r @"PresentationCore.dll"
#r @"PresentationFramework.dll"

open System
open System.IO
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media
open System.Windows.Media.Imaging
//----------------------------------------------------------
// From Chapter 16 RecurseDirectoriesIncrementally
//----------------------------------------------------------
let selectedImage = new BitmapImage(new Uri("file:///icons/OPENFOLD.BMP"))
let unselectedImage = new BitmapImage(new Uri("file:///icons/CLSDFOLD.BMP"))
let cddriveImage = new BitmapImage(new Uri("file:///icons/CDDRIVE.BMP"))
let driveImage = new BitmapImage(new Uri("file:///icons/DRIVE.BMP"))
let floppyImage = new BitmapImage(new Uri("file:///icons/35FLOPPY.BMP"))

//
// From Chapter 16 – ImagedTreeViewItem
//
type ImagedTreeViewItem() = class
inherit TreeViewItem() as base

let stack = new StackPanel(Orientation=Orientation.Horizontal)
let img = new Image(VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(0.0, 0.0, 2.0, 0.0))
let text = new TextBlock(VerticalAlignment = VerticalAlignment.Center)
let mutable srcSelected:ImageSource = null
let mutable srcUnselected:ImageSource = null

do
base.Header <- stack
img |> stack.Children.Add |> ignore
text |> stack.Children.Add |> ignore

member this.Text
with get() = text.Text
and set value =
text.Text <- value

member this.SelectedImage
with get() = srcSelected
and set value =
srcSelected <- value
if (this.IsSelected) then img.Source <- srcSelected

member this.UnselectedImage
with get() = srcUnselected
and set value =
srcUnselected <- value
if (this.IsSelected=false) then img.Source <- srcUnselected

override this.OnSelected (args:RoutedEventArgs ) =
base.OnSelected(args)
img.Source <- selectedImage

override this.OnUnselected (args:RoutedEventArgs ) =
base.OnUnselected(args)
img.Source <- unselectedImage

end

//
// From Chapter 16 – DirectoryTreeViewItem
//
type DirectoryTreeViewItem = class
inherit ImagedTreeViewItem as base

val mutable dir:DirectoryInfo

new (rootdir:DirectoryInfo) as this = {dir=rootdir} then
base.Text <- this.dir.Name

member this.DirectoryInfo
with get() = this.dir

member this.Populate () =
try
this.dir.GetDirectories()
|> Seq.iter (fun child ->
new DirectoryTreeViewItem(child)
|> this.Items.Add |> ignore)

with _ -> ()

override this.OnExpanded (args:RoutedEventArgs) =
base.OnExpanded(args)

this.Items |> Seq.untyped_to_typed
|> Seq.iter (fun (item:DirectoryTreeViewItem) ->
item.Populate())

end

//
// From Chapter 16 – DirectoryTreeView
//
type DirectoryTreeView() = class
inherit TreeView() as base

member this.RefreshTree() =
this.BeginInit()
this.Items.Clear()

DriveInfo.GetDrives() |> Seq.iter (fun (drive:DriveInfo) ->
let chDrive = String.get (drive.Name|> String.capitalize) 0
let item = new DirectoryTreeViewItem(drive.RootDirectory)
if (chDrive <> 'A' && chDrive <> 'B' && drive.IsReady && drive.VolumeLabel.Length > 0) then
item.Text <- String.Format("{0} ({1})",drive.VolumeLabel,drive.Name)
else
item.Text <- String.Format("{0} ({1})",drive.DriveType,drive.Name)

if (chDrive = 'A' || chDrive = 'B') then
item.SelectedImage <- floppyImage
item.UnselectedImage <- floppyImage
else if (drive.DriveType = DriveType.CDRom) then
item.SelectedImage <- cddriveImage
item.UnselectedImage <- cddriveImage
else
item.SelectedImage <- driveImage
item.UnselectedImage <- driveImage

//item.Selected.Add(fun _ -> ())

item |> this.Items.Add |> ignore
if (chDrive <> 'A' && chDrive <> 'B' && drive.IsReady) then
item.Populate()
this.EndInit()
)
end


//
// From Chapter 16 – RecurseDirectoriesIncrementally
//
let tree = new DirectoryTreeView()
tree.RefreshTree()
let stack = new StackPanel()

tree.SelectedItemChanged.Add(fun args ->
let item = args.NewValue :?> DirectoryTreeViewItem
stack.Children.Clear()

try
item.DirectoryInfo.GetFiles() |> Seq.iter (fun info ->
let text = new TextBlock(Text=info.Name)
text |> stack.Children.Add |> ignore)
with _ -> ()
)

let grid = new Grid()

[new ColumnDefinition(Width=new GridLength(50.0, GridUnitType.Star));
new ColumnDefinition(Width=GridLength.Auto);
new ColumnDefinition(Width=new GridLength(50.0, GridUnitType.Star))]
|> List.iter grid.ColumnDefinitions.Add

tree |> grid.Children.Add
Grid.SetColumn(tree,0)

let split = new GridSplitter(Width=6.0,
ResizeBehavior = GridResizeBehavior.PreviousAndNext)
split |> grid.Children.Add
Grid.SetColumn(split,1)

let scroll = new ScrollViewer(Content = stack)
scroll |> grid.Children.Add
Grid.SetColumn(scroll,2)

let window = new Window(Title="Recurse Directories Incrementally",
Content=grid)

#if COMPILED
[<STAThread()>]
do
let app = Application() in
app.Run(window) |> ignore
#endif

TemplateTheTree & DiskDirectory

#light
#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0"
#r @"WindowsBase.dll"
#r @"PresentationCore.dll"
#r @"PresentationFramework.dll"

open System
open System.IO
open System.Windows
open System.Windows.Controls
open System.Windows.Data
open System.Windows.Input
open System.Windows.Media
open System.Windows.Media.Imaging
//
// From Chapter 16 TemplateTheTree & DiskDirectory
//
//----------------------------------------------------------

type DiskDirectory = class

val mutable dirinfo:DirectoryInfo

new (rootdir:DirectoryInfo) as this = {dirinfo=rootdir}

member this.Name
with get() = this.dirinfo

member this.Subdirectories
with get() =
let dirs = new ResizeArray<DiskDirectory>()
try
this.dirinfo.GetDirectories() |> Seq.iter (fun subdir ->
dirs.Add(new DiskDirectory(subdir)))
with _ -> ()
dirs


end

let treevue = new TreeView()

let window = new Window(Title="Template the Tree",
Content=treevue)

let template = new HierarchicalDataTemplate(typeof<DiskDirectory>)
template.ItemsSource <- new Binding("Subdirectories")
let factoryTextBlock = new FrameworkElementFactory(typeof<TextBlock>)

(TextBlock.TextProperty, new Binding("Name"))
|> factoryTextBlock.SetBinding

template.VisualTree <- factoryTextBlock

let dir = new DiskDirectory(new DirectoryInfo(Path.GetPathRoot(Environment.SystemDirectory)))

treevue.Items.Add
(let item = new TreeViewItem(Header=dir.Name,
IsExpanded=true,
ItemsSource=dir.Subdirectories,
ItemTemplate=template)
item)

#if COMPILED
[<STAThread()>]
do
let app = Application() in
app.Run(window) |> ignore
#endif

ShowClassHierarchy

#light
#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0"
#r @"WindowsBase.dll"
#r @"PresentationCore.dll"
#r @"PresentationFramework.dll"

open System
open System.Collections.Generic
open System.Reflection
open System.Windows
open System.Windows.Controls
//
// From Chapter 16 - ShowClassHierarchy
//
//----------------------------------------------------------

type TypeTreeViewItem = class
inherit TreeViewItem as base

val mutable mytype:Type

new () as this = {mytype=null}

member this.Type
with get() = this.mytype
and set (value:Type) =
this.mytype <- value
if (this.mytype.IsAbstract) then
base.Header <- (this.mytype.Name + " (abstract)")
else
base.Header <- this.mytype.Name


end

type ClassHierarchyTreeView = class
inherit TreeView as base

new (typeRoot:Type) as this = {} then
let dummy = new UIElement()
let assemblies = new ResizeArray<Assembly>()
Assembly.GetExecutingAssembly().GetReferencedAssemblies()
|> Seq.iter (fun name -> assemblies.Add(Assembly.Load(name)))
let classes = new SortedList<string,Type>()
classes.Add(typeRoot.Name,typeRoot)

assemblies |> Seq.iter (fun assembly ->
assembly.GetTypes() |> Seq.iter (fun t ->
if (t.IsPublic && t.IsSubclassOf(typeRoot)) then
classes.Add(t.Name,t)))

// create root item
let item = new TypeTreeViewItem(Type=typeRoot)
item |> this.Items.Add |> ignore

// Add recursively
this.CreateLinkedItems item classes



member this.CreateLinkedItems (itemBase:TypeTreeViewItem) (list:SortedList<string,Type>) =
list |> Seq.iter (fun keypair ->
if (keypair.Value.BaseType = itemBase.Type) then
let item = new TypeTreeViewItem(Type=keypair.Value)
itemBase.Items.Add(item) |> ignore
this.CreateLinkedItems item list
)

end

let treevue = new ClassHierarchyTreeView(typeof<System.Windows.Threading.DispatcherObject>)

let window = new Window(Title="Show Class Hierarchy",
Content=treevue)


#if COMPILED
[<STAThread()>]
do
let app = Application() in
app.Run(window) |> ignore
#endif

ListSystemParameters

#light
#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0"
#r @"WindowsBase.dll"
#r @"PresentationCore.dll"
#r @"PresentationFramework.dll"

open System
open System.Collections.Generic
open System.ComponentModel
open System.Reflection
open System.Windows
open System.Windows.Controls
open System.Windows.Data
open System.Windows.Input
open System.Windows.Media
//
// From Chapter 16 - ListSystemParameters
//
//----------------------------------------------------------
type SystemParam() = class
let mutable strName=""
let mutable objvalue = null

member this.Name
with get() = strName
and set (value:string) = strName <- value

member this.Value
with get() = objvalue
and set (value:obj) = objvalue <- value

override this.ToString() =
this.Name + "=" + this.Value.ToString()
end


let grdvue = new GridView()
let lstvue = new ListView(View=grdvue)

// Create two GridView columns.
new GridViewColumn(Header="Property Name",
Width=200.0,
DisplayMemberBinding = new Binding("Name"))
|> grdvue.Columns.Add

new GridViewColumn(Header="Value",
Width=200.0,
DisplayMemberBinding = new Binding("Value"))
|> grdvue.Columns.Add


typeof<SystemParameters>.GetProperties() |> Seq.iter (fun prop ->
if (prop.PropertyType <> typeof<ResourceKey>) then
new SystemParam(Name=prop.Name,
Value = prop.GetValue(null,null))
|> lstvue.Items.Add |> ignore
)

let window = new Window(Title="List System Parameters",
Content=lstvue)


#if COMPILED
[<STAThread()>]
do
let app = Application() in
app.Run(window) |> ignore
#endif

ListSortedSystemParameters

#light
#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0"
#r @"WindowsBase.dll"
#r @"PresentationCore.dll"
#r @"PresentationFramework.dll"

open System
open System.Collections.Generic
open System.ComponentModel
open System.Reflection
open System.Windows
open System.Windows.Controls
open System.Windows.Data
open System.Windows.Input
open System.Windows.Media
//
// From Chapter 16 - ListSortedSystemParameters
//
//----------------------------------------------------------
type SystemParam() = class
let mutable strName=""
let mutable objvalue = null

member this.Name
with get() = strName
and set (value:string) = strName <- value

member this.Value
with get() = objvalue
and set (value:obj) = objvalue <- value

override this.ToString() =
this.Name + "=" + this.Value.ToString()
end


let grdvue = new GridView()
let lstvue = new ListView(View=grdvue)

// Create two GridView columns.
new GridViewColumn(Header="Property Name",
Width=200.0,
DisplayMemberBinding = new Binding("Name"))
|> grdvue.Columns.Add


// Create DataTemplate for second column
let template = new DataTemplate(typeof<string>)
let factoryTextBlock = new FrameworkElementFactory(typeof<TextBlock>)
(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Right)
|> factoryTextBlock.SetValue

(TextBlock.TextProperty,new Binding("Value"))
|> factoryTextBlock.SetBinding

template.VisualTree <- factoryTextBlock

new GridViewColumn(Header="Value",
Width=200.0,
CellTemplate=template,
DisplayMemberBinding = new Binding("Value"))
|> grdvue.Columns.Add

let sortlist = new SortedList<string,SystemParam>()

typeof<SystemParameters>.GetProperties() |> Seq.iter (fun prop ->
if (prop.PropertyType <> typeof<ResourceKey>) then
(prop.Name,
new SystemParam(Name=prop.Name,
Value = prop.GetValue(null,null)))
|> sortlist.Add
)
lstvue.ItemsSource <- sortlist.Values

let window = new Window(Title="List Sorted System Parameters",
Content=lstvue)


#if COMPILED
[<STAThread()>]
do
let app = Application() in
app.Run(window) |> ignore
#endif

ExploreDependencyProperties

#light
#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0"
#r @"WindowsBase.dll"
#r @"PresentationCore.dll"
#r @"PresentationFramework.dll"
#r @"classhierarchytreeview.dll"

open System
open System.Collections.Generic
open System.ComponentModel
open System.Globalization
open System.Reflection
open System.Windows
open System.Windows.Controls
open System.Windows.Data
open Chapter16 // for ClassHierarchyTreeView
//
// From Chapter 16 - ExploreDependencyProperties
//
//----------------------------------------------------------
type TypeToString() =
interface IValueConverter with
member v.Convert (o:obj,t:Type,param:obj,culture:CultureInfo) =
(o :?> Type).Name :> obj

member v.ConvertBack (o:obj,t:Type,param:obj,culture:CultureInfo) = null


type MetadataToFlags() =
interface IValueConverter with
member v.Convert (o:obj,t:Type,param:obj,culture:CultureInfo) =
// Need to wrap with try/with for those objects that
// only can cast downto PropertyMetadata
try
let metadata = o :?> FrameworkPropertyMetadata

let checks = [metadata.AffectsMeasure;
metadata.AffectsArrange;
metadata.AffectsParentMeasure;
metadata.AffectsParentArrange;
metadata.AffectsRender;
metadata.Inherits;
metadata.OverridesInheritanceBehavior;
metadata.IsNotDataBindable;
metadata.BindsTwoWayByDefault;
metadata.Journal]

let options = [FrameworkPropertyMetadataOptions.AffectsMeasure;
FrameworkPropertyMetadataOptions.AffectsArrange;
FrameworkPropertyMetadataOptions.AffectsParentMeasure;
FrameworkPropertyMetadataOptions.AffectsParentArrange;
FrameworkPropertyMetadataOptions.AffectsRender;
FrameworkPropertyMetadataOptions.Inherits;
FrameworkPropertyMetadataOptions.OverridesInheritanceBehavior;
FrameworkPropertyMetadataOptions.NotDataBindable;
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault;
FrameworkPropertyMetadataOptions.Journal;]

// Instead of a bunch of if statements, I replaced it with
// zip, filter, map and combine...
let flags =
Seq.zip checks options
|> Seq.filter (fun (check,option) -> check)
|> Seq.map (fun (check,option) -> option)
|> Seq.to_list
|> Enum.combine

flags :> obj
with _ ->
FrameworkPropertyMetadataOptions.None :> obj


member v.ConvertBack (o:obj,t:Type,param:obj,culture:CultureInfo) =
let options = o :?> FrameworkPropertyMetadataOptions
new FrameworkPropertyMetadata(null,options) :> obj


let mutable private initTypeProperty : DependencyProperty = null

type DependencyPropertyListView () = class
inherit ListView() as base

do
let grdvue = new GridView()
base.View <- grdvue

let buildTemplate (bind:Binding) converter =
let elTextBlock =new FrameworkElementFactory(typeof<TextBlock>)
let template = new DataTemplate(VisualTree=elTextBlock)
bind.Converter <- converter
(TextBlock.TextProperty,bind) |> elTextBlock.SetBinding
template


let ownerTemplate = buildTemplate (new Binding("OwnerType")) (new TypeToString())
let flagsTemplate = buildTemplate (new Binding("DefaultMetadata")) (new MetadataToFlags())

let coldefs =
[("Name",Some("Name"),None,150.0);
("Owner",None,Some(ownerTemplate),150.0);
("Default",Some("DefaultMetadata.DefaultValue"),None,75.0);
("Read-Only",Some("DefaultMetadata.ReadOnly"),None,75.0);
("Usage",Some("DefaultMetadata.AttachedPropertyUsage"),None,75.0);
("Flags",None,Some(flagsTemplate),250.0);]

coldefs |> Seq.iter (fun (header,bindingOption,templateOption,width) ->
match bindingOption with
| Some binding ->
new GridViewColumn(Header=header,
DisplayMemberBinding = new Binding(binding),
Width=width)
|> grdvue.Columns.Add

| None -> ()

match templateOption with
| Some template ->
new GridViewColumn(Header=header,
CellTemplate = template,
Width=width)
|> grdvue.Columns.Add
| None -> ())

static member TypeProperty =
if initTypeProperty = null then
let metadata =
new PropertyMetadata(null, new PropertyChangedCallback(DependencyPropertyListView.OnTypePropertyChanged))

initTypeProperty <-
DependencyProperty.Register
("Type",
typeof<Type>,
typeof<DependencyPropertyListView>,
metadata)

initTypeProperty

static member OnTypePropertyChanged (obj:DependencyObject) (args:DependencyPropertyChangedEventArgs) =
let lstvue = obj :?> DependencyPropertyListView
let t = args.NewValue :?> Type
lstvue.ItemsSource <- null
if t <> null then
let list = new SortedList<string,DependencyProperty>()
t.GetFields() |> Seq.iter (fun info ->
if info.FieldType = typeof<DependencyProperty> then
(info.Name,(info.GetValue(null) :?> DependencyProperty))
|> list.Add)
lstvue.ItemsSource <- list.Values

member this.Type
with get() =
let value = this.GetValue(DependencyPropertyListView.TypeProperty)
(value :?> DependencyProperty)
and set (value:DependencyProperty) = this.SetValue(DependencyPropertyListView.TypeProperty,value)

end


let treevue = new Chapter16.ClassHierarchyTreeView(typeof<DependencyObject>)
let grid = new Grid()

// create 3 column definitions for Grid
[new GridLength(1.0,GridUnitType.Star);
GridLength.Auto;
new GridLength(3.0,GridUnitType.Star)]
|> Seq.iter (fun width ->
new ColumnDefinition(Width=width) |> grid.ColumnDefinitions.Add)

treevue |> grid.Children.Add
Grid.SetColumn(treevue,0)

let split = new GridSplitter(HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Stretch,
Width = 6.0)
split |> grid.Children.Add
Grid.SetColumn(split,1)

let lstvue = new DependencyPropertyListView(DataContext=treevue)
lstvue |> grid.Children.Add
Grid.SetColumn(lstvue,2)

(DependencyPropertyListView.TypeProperty, "SelectedItem.Type")
|> lstvue.SetBinding

let window = new Window(Title="Explore Dependency Properties",
Content=grid)


#if COMPILED
[<STAThread()>]
do
let app = Application() in
app.Run(window) |> ignore
#endif

2 comments:

Anonymous said...

Drag TreeView control and drop to ListView Form

Blogger said...

Are you looking to earn cash from your visitors with popunder ads?
If so, have you ever used ExoClick?