在WPF的TreeView使用方式和WinForm下有很大不同,那些展开某节点、获取父节点,判断某节点是否被选中等常用的操作在WinForm下都有相关函数,而在WPF中却不能轻易实现。
一种常规的方式是通过MVVM模式来将TreeViewItem节点中的IsSelect,IsExpanded等属性来双向绑定到要显示的节点数据中,然后直接通过节点数据的属性来实现相关操作。
MVVM的设计方式本身是为了分离了逻辑与界面,目的就是解放业务逻辑,使得在做设计时,把注意力更集中在业务逻辑上,而不考虑过多的UI呈现。这种方式带来的好处是不言而喻的,但是,很多时候,我们的业务数据中是不关心IsSelect,IsExpanded等属性的,此时非要把这几个UI强关联的属性强加进来就显得非常格格不入,也增加了设计时的复杂性。
同时,这种设计方式一开始就把TreeView和数据节点的呈现形式强制关联起来了,使得开发人员往往需要考虑IsExpand或IsSelect等属性带来的影响,实际上也是增加了业务逻辑和UI的耦合。万一客户一时不爽,要我们把TreeView换成ListBox,又得修改原始的业务逻辑了。
有鉴于此,如果我们业务逻辑不想关心IsSelect,IsExpanded等属性,但同时在UI层又需要对其设置时,该如何办呢。其实WPF还是提供了类似WinForm中的这些设置的,只不过形式不一样了而已,但是却没WinFrom的那么直观和方便。CodeProject上就有人将常用函数总结了一下,写成了扩展函数,主要提供如下功能:
public static void SelectObject(this TreeView treeView, object obj)
public static void SelectObject(this TreeView treeView, object obj, bool selected)
public static bool IsObjectSelected(this TreeView treeView, object obj)
public static bool IsObjectFocused(this TreeView treeView, object obj)
public static void ExpandObject(this TreeView treeView, object obj)
public static void ExpandObject(this TreeView treeView, object obj, bool expanded)
public static bool IsObjectExpanded(this TreeView treeView, object obj)
public static TreeViewItem GetParentItem(this TreeViewItem item)
文章地址如下:WPF TreeView tools
但是,这里面有一个小bug:当TreeView节点中使用延迟绑定的时候,根据数据节点获取TreeItem会失败。这里我把它修正了一下,感兴趣的朋友可以直接使用我修改后的函数。

代码
public static class TreeViewTools
{
/// <summary>
/// Returns the TreeViewItem of a data bound object.
/// </summary>
/// <param name="treeView">TreeView</param>
/// <param name="obj">Data bound object</param>
/// <returns>The TreeViewItem of the data bound object or null.</returns>
public static TreeViewItem GetItemFromObject(this TreeView treeView, object obj)
{
try
{
DependencyObject dObject = GetContainerFormObject(treeView, obj);
TreeViewItem tvi = dObject as TreeViewItem;
while (tvi == null)
{
dObject = VisualTreeHelper.GetParent(dObject);
tvi = dObject as TreeViewItem;
}
return tvi;
}
catch { }
return null;
}
private static DependencyObject GetContainerFormObject(ItemsControl item, object obj)
{
if (item == null)
return null;
DependencyObject dObject = null;
dObject = item.ItemContainerGenerator.ContainerFromItem(obj);
if (dObject != null)
return dObject;
var query = from childItem in item.Items.Cast<object>()
let childControl = item.ItemContainerGenerator.ContainerFromItem(childItem) as ItemsControl
select GetContainerFormObject(childControl, obj);
return query.FirstOrDefault(i => i != null);
}
/// <summary>
/// Selects a data bound object of a TreeView.
/// </summary>
/// <param name="treeView">TreeView</param>
/// <param name="obj">Data bound object</param>
public static void SelectObject(this TreeView treeView, object obj)
{
treeView.SelectObject(obj, true);
}
/// <summary>
/// Selects or deselects a data bound object of a TreeView.
/// </summary>
/// <param name="treeView">TreeView</param>
/// <param name="obj">Data bound object</param>
/// <param name="selected">select or deselect</param>
public static void SelectObject(this TreeView treeView, object obj, bool selected)
{
var tvi = treeView.GetItemFromObject(obj);
if (tvi != null)
{
tvi.IsSelected = selected;
}
}
/// <summary>
/// Returns if a data bound object of a TreeView is selected.
/// </summary>
/// <param name="treeView">TreeView</param>
/// <param name="obj">Data bound object</param>
/// <returns>Returns true if the object is selected, and false if it is not selected or obj is not in the tree.</returns>
public static bool IsObjectSelected(this TreeView treeView, object obj)
{
var tvi = treeView.GetItemFromObject(obj);
if (tvi != null)
{
return tvi.IsSelected;
}
return false;
}
/// <summary>
/// Returns if a data bound object of a TreeView is focused.
/// </summary>
/// <param name="treeView">TreeView</param>
/// <param name="obj">Data bound object</param>
/// <returns>Returns true if the object is focused, and false if it is not focused or obj is not in the tree.</returns>
public static bool IsObjectFocused(this TreeView treeView, object obj)
{
var tvi = treeView.GetItemFromObject(obj);
if (tvi != null)
{
return tvi.IsFocused;
}
return false;
}
/// <summary>
/// Expands a data bound object of a TreeView.
/// </summary>
/// <param name="treeView">TreeView</param>
/// <param name="obj">Data bound object</param>
public static void ExpandObject(this TreeView treeView, object obj)
{
treeView.ExpandObject(obj, true);
}
/// <summary>
/// Expands or collapses a data bound object of a TreeView.
/// </summary>
/// <param name="treeView">TreeView</param>
/// <param name="obj">Data bound object</param>
/// <param name="expanded">expand or collapse</param>
public static void ExpandObject(this TreeView treeView, object obj, bool expanded)
{
var tvi = treeView.GetItemFromObject(obj);
if (tvi != null)
{
tvi.IsExpanded = expanded;
if (expanded)
{
// update layout, so that following calls to f.e. SelectObject on child nodes will
// find theire TreeViewNodes
treeView.UpdateLayout();
}
}
}
/// <summary>
/// Returns if a douta bound object of a TreeView is expanded.
/// </summary>
/// <param name="treeView">TreeView</param>
/// <param name="obj">Data bound object</param>
/// <returns>Returns true if the object is expanded, and false if it is collapsed or obj is not in the tree.</returns>
public static bool IsObjectExpanded(this TreeView treeView, object obj)
{
var tvi = treeView.GetItemFromObject(obj);
if (tvi != null)
{
return tvi.IsExpanded;
}
return false;
}
/// <summary>
/// Retuns the parent TreeViewItem.
/// </summary>
/// <param name="item">TreeViewItem</param>
/// <returns>Parent TreeViewItem</returns>
public static TreeViewItem GetParentItem(this TreeViewItem item)
{
var dObject = VisualTreeHelper.GetParent(item);
TreeViewItem tvi = dObject as TreeViewItem;
while (tvi == null)
{
dObject = VisualTreeHelper.GetParent(dObject);
tvi = dObject as TreeViewItem;
}
return tvi;
}
}
posted @
2010-02-06 21:56 天方 阅读(17) |
评论 (0) |
编辑
在.Net 4.0中增加了一系列较为实用的IO功能,下面让我们来一起看一下吧:
1. Stream.CopyTo
Stream.CopyTo在用于较小的Stream之间的拷贝时还是比较方便的,有了它后我就不用为这个简单的功能而再写一个扩展函数了。当然,这个函数不适合于大型的Stream的拷贝(延迟太高),要是微软肯再加上一个带进度的就更好了。O(∩_∩)O~
2. File.ReadLines和File.WriteAllLines
在.Net 2.0时代,当我们读一个文本文件的时候,往往是用的File.ReadAllLines方法读取所有的行,然后通过遍历所有行来进行相关的文件操作,如下所示:
var lines = File.ReadAllLines("1.txt");
foreach (var line in lines)
{
//....
}
这种方式简单有效,但也存在一个非常严重的问题:当文件很大的时候,读取所有行需要占用大量的时间和内存。并且如果我们若只需要在文件中查询部分内容的时候,也无法在读到有效内容后放弃继续读取。当然,这些不足可以通过StreamReader来解决,如下所示:
using (var reader = new
StreamReader("1.txt"))
{
string line;
while ((line = reader.ReadLine()) != null)
{
//...
}
}
但这个方法显然不如上面的File.ReadAllLines来的直观方便,并且还牵涉到Stream资源无法及时释放的隐患。
在.Net 4.0中,引入了File.ReadLines函数,该函数使用方式与File.ReadAllLines是一致的:
var lines = File.ReadLines("1.txt");
foreach (var line in lines)
{
//....
}
与File.ReadAllLines不同的是:File.ReadAllLines返回的是string[],而File.ReadLines返回的是IEnumerable<string>。也就是说,File.ReadLines是延迟执行的,在保持着File.ReadAllLines的简单直观的特点同时,没有其在处理大文件时候性能方面的不足,完全可以取代File.ReadAllLines函数。
与其对应的,File.WriteAllLines也增加了支持IEnumerable<string>的入参的重载形式,同样解决了的大文本的写入时的性能问题。
3. 遍历文件夹
在.Net 2.0中,要获取某个文件夹中包括子文件夹的所有的文件时,可以简单地通过Directory.GetFiles的实现:
Directory.GetFiles(@"R:\","*.*", SearchOption.AllDirectories);
但我们却大多不采取这种方法,因为在无法预计其文件的数量情况下,这个方法带来的高延时和高内存占用往往会导致程序或用户的崩溃。
在.Net 4.0中,为Directory类增加了三个遍历用的方法:
Directory.EnumerateFiles
Directory.EnumerateDirectories
Directory.EnumerateFileSystemEntries
和上面的File.ReadLines一样,主要是为了解决海量查询时的性能问题的,和LINQ配合使用则更是如虎添翼。由于使用方法和之前的Get系列毫无二致,这里就不介绍了。
4. 内存映射文件
内存映射文件的概念在Windows早就存在,在进程间大量数据交互时无疑是最高效的手段,以前要使用它只能通过调用API来实现,现在.Net 4.0已经内置了其的支持,使用起来还是非常方便的。一个简单示例如下:
using (var file = MemoryMappedFile.CreateNew("MemoryMappedFile", 1024))
{
using (var bw = new
BinaryWriter(file.CreateViewStream()))
{
bw.Write("hello world");
Console.ReadKey();
}
}
using (MemoryMappedFile file = MemoryMappedFile.OpenExisting("MemoryMappedFile"))
{
using (BinaryReader br = new
BinaryReader(file.CreateViewStream()))
{
Console.WriteLine(br.ReadString());
}
}
内存映射文件的作用非常多,关于介绍这方面的文章网上也有许多,有兴趣的朋友可以参看一下这个链接:http://webservices.ctocio.com.cn/net/62/9129562.shtml
posted @
2010-02-05 21:30 天方 阅读(21) |
评论 (0) |
编辑
F#的class赋予了F#面向对象的编程能力,也是F#连接.net中其它面向对象语言的桥梁。其基本形式如下:
// Class definition:
type [access-modifier] type-name [type-params]( parameter-list ) [ as identifier ] =
[ class ]
[ inherit base-type-name(base-constructor-args) ]
[ let-bindings ]
[ do-bindings ]
member-list
...
[ end ]
// Mutually recursive class definitions:
type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...
构造函数
首先我们来通过一个简单的示例来介绍F#中的构造函数:
type Point(x:int,y:int) =
new() = Point(0,0)
new(x:int) = Point(x,0)
它的类型为:
type Point =
class
new : unit -> Point
new : x:int -> Point
new : x:int * y:int –> Point
end
从中可以看出,这三行分别定义了三个构造函数,首先第一行type Point(x:int,y:int)定义了一个初始的构造函数,它是所有其它构造函数的入口。
第二行new()和第三行new(x:int)分别基于初始构造函数定义了两个新的构造函数,这个Point类的等效C#代码为:
public class Point
{
public Point() : this(0, 0) { }
public Point(int x) : this(x, 0) { }
public Point(int x, int y) { }
}
初始化:
进入构造函数后,我们第一步要做的是初始化,初始化工作一般是通过do绑定来完成的,我们把上面那个Point类改造一下,在构造函数中加入一个打印x和y值的功能:
type Point(x:int,y:int) =
do
printfn "(%d,%d)" x y
new() = Point(0,0)
new(x:int) = Point(x,0)
do绑定可以看做是在初始构造函数的函数体,也就是说,其它所有的构造函数都会执行do绑定的表达式。
另外,如果要实现更复杂的初始化操作,还牵涉到了let绑定,关于let绑定和do绑定更多信息,在后续介绍F#对象中的成员时再做进一步介绍。
基类
在F#中,通过关键字inherit指明了对象的基类,这一点其实和C#很相似,只是语法结构不一样,这里只举一个简单的例子,后续介绍F#对象的继承时再做详细介绍。
type MyClassBase1() =
let mutable z = 0
abstract member function1 : int –> int
default u.function1(a : int) = z <- z + a; z
type MyClassDerived1() =
inherit MyClassBase1()
override u.function1(a: int) = a + 1
自标示符(Self Identifiers)
在C#和C++中,可以通过this关键字引用当前对象实例的成员;但在F#中,这种标示自己的标示符并不是特定的,可以通过as来生命为任意一个名称,示例如下:
type MyClass2(data_in) as self =
let data = data_in
do
self.PrintMessage()
member this.PrintMessage() =
printf "Creating MyClass2 with Data %d" data
成员
F#对象的成员非常丰富,总体来说大致有如下几种:
-
let绑定
-
do绑定
-
成员变量
-
属性
-
索引
-
方法
-
运算符重载
-
事件
由于牵涉面较大,这里就不介绍了,后续再单独写文章介绍这部分内容。
posted @
2010-01-24 19:51 天方 阅读(25) |
评论 (0) |
编辑
Structure是F#的基本类型之一,和C#中的struct对应,其语法结构如下:
[ attributes ]
type [accessibility-modifier] type-name =
struct
type-definition-elements
end
一个简单的struct定义为
type Point3D =
struct
val x: float
val y: float
val z: float
end
但这种方式并不是lightweight,实际也很少这么使用,通过lightweight语法简化后的形式为:
[ attributes ]
[<StructAttribute>]
type [accessibility-modifier] type-name =
type-definition-elements
因此,我们用这种方式也能到达上述同样的效果:
type Point3D2 =
val x: float
val y: float
val z: float
Structure不能使用let或do绑定,只能通过val声明成员变量。因此,其各成员是有默认值的。另外,它也能说明成员函数,由于这部分内容和class比较类似,这里就不做详细介绍了。
posted @
2010-01-24 18:17 天方 阅读(19) |
评论 (0) |
编辑
定义Discriminated Unions:
Discriminated Unions是F#的一种特有的数据类型,其基本语法格式如下:
type type-name =
| case-identifier1 [of type1 [ * type2 ...]
| case-identifier2 [of type3 [ * type4 ...]
...
Discriminated Unions主要用来表示在一组具有同一类型的子类型(可以看做一组具有共同基类的子类)。例如,我们要表示一套扑克中的四种花色:红桃、方块、梅花、黑桃,可以表示如下:
type Suit =
| Heart
| Diamond
| Spade
| Club
接下来,我们可以继续扩充它,来表示一整幅牌。在一副牌中,每张牌都都有四种类型。除了A、J、Q、K四张牌外,剩下的1到10都可以利用一个元组类型来关联四种牌型。则可以表示如下:
type ValueCard =
| Ace of Suit
| King of Suit
| Queen of Suit
| Jack of Suit
| ValueCard of int * Suit
let deckOfCards =
[
for suit in [ Spade; Club; Heart; Diamond ] do
yield Ace(suit)
yield King(suit)
yield Queen(suit)
yield Jack(suit)
for value in 2 .. 10 do
yield ValueCard(value, suit)
]
使用Discriminated Unions:
Discriminated Unions经常和模式匹配一起使用,例如,我们定义扑克牌的出牌法则如下:
let describeHoleCards cards =
match cards with
| [] | [_]
-> failwith "Too few cards."
| cards when List.length cards > 2
-> failwith "Too many cards."
| [ Ace(_); Ace(_) ] -> "Pocket Rockets"
| [ King(_); King(_) ] -> "Cowboys"
| [ ValueCard(2, _); ValueCard(2, _)] -> "Ducks"
| [ Queen(_); Queen(_) ]
| [ Jack(_); Jack(_) ]
-> "Pair of face cards"
| [ ValueCard(x, _); ValueCard(y, _) ] when x = y
-> "A Pair"
| [ first; second ]
-> sprintf "Two cards: %A and %A" first second
非常优雅而强大,这是那些没有模式匹配的语言所无法比拟的。
用C#实现Discriminated Unions
正所谓条条大路通罗马,这种扑克牌的功能也可以用C#表示,首先拿花色来说,最直接的反应便是用枚举实现:
enum Suit { Heart, Diamond, Spade, Club }
然而这种方式存在一定隐患:我们可以很容易写出通过编译器检查的非法的花色
var invalidSuit1 = Suit.Club | Suit.Diamond;
var invalidSuite2 = (Suit) - 1;
要避免这种隐患,可以通过如下两种方式:
方式1:
class Suite
{
private Suite() { }
static Suite()
{
Heart = new Suite();
Diamond = new Suite();
Spade = new Suite();
Club = new Suite();
}
public static Suite Heart { get; private set; }
public static Suite Diamond { get; private set; }
public static Suite Spade { get; private set; }
public static Suite Club { get; private set; }
}
方式2:
abstract class Suite { }
class Heard : Suite { }
class Diamond : Suite { }
class Spade : Suite { }
class Club : Suite { }
由于F#和C#本身是两种不同特性的语言,没有必要把其实现和C#一一对应起来(虽然确实可以一一对应起来),我并没有通过Reflector来看其具体对应着C#的实现。仅仅从功能上来讲,这两种方式都实现了我们预期的功能。 但Discriminated Unions存在如下特性:
-
可实例化
-
union case可以为不同的类型
根据这两个特性来看,方式1不满足这两个条件,而方式2基本上和Discriminated Unions是等价的。接下来定义ValueCard时,方式1就不能满足需求,而方式2却仍然适用。因此,就像我前面所说的那样,Discriminated Unions可以看做一组具有共同基类的子类。
由于C#没有模式匹配,在接下来扑克牌规则的实现时,C#只能通过无数的if来模拟这种功能,代码几乎是F#的三倍左右,并且可维护性非常差。在这方面,F#以其独有的语法特性占有绝对的优势。
参考文章:
http://blog.csdn.net/minyskirt/archive/2009/12/15/5010779.aspx
posted @
2010-01-24 16:24 天方 阅读(22) |
评论 (0) |
编辑
Records是F#里用来表示简单数据的一种基本数据类型,当你想把数据组成一个结构化的格式,而不需要太复杂的语法时,你可以使用Record类型。
声明Records类型:
Records的语法格式如下:
[ attributes ]
type [accessibility-modifier] typename = {
[ mutable ] label1 : type1;
[ mutable ] label2 : type2;
...
}
member-list
这里我们定义一个简单的Point类型:
type Point = { x : float; y: float; }
创建Records对象:
声明Point类型后,就可以定义Point对象了:
let mypoint1 = { x = 1.0; y = 1.0; }
let mypoint2 = { new Point with x = 1.0 and y = 1.0 }
这里我使用了两种方式创建了Point对象,mypoint1并没有指明类型,F#会自动根据其成员推导出其类型。但如果有两个不同的Records具有同样的成员,则会产生二义性,如下所示:
type Point = { x : float; y: float; }
type Point2D = { x: float; y: float; }
let mypoint1 = { x = 1.0; y = 1.0; }
let mypoint2 = { new Point with x = 1.0 and y = 1.0 }
这个时候,mypoint1就可能和两种类型Point和Point2D都对应,虽然这个时候有了二义性,但本身还是没有语法错误的,系统会自动将mypoint1推导为后定义的point2D类型。如果要定义Point类型,则只有使用mypoint2这种制定类型的方式创建。
另外,还可以通过表达式的方式创建Point对象:
let mypoint3 = { mypoint1 with y=5.0 }
//val mypoint3 : Point = {x = 1.0; y = 5.0;}
这里mypoint3继承了mypoint对象,并重新定义了y对象,非常直观,就不多讲了。
定义方法和属性:
定义方法和属性非常简单,示例如下:
type Point = { x : float; y: float; }
with
member p.Length =
sqrt <| p.x ** 2.0 + p.y ** 2.0
member p.Show() =
printf "%A" p
模式匹配
Records的模式匹配和Tuples类似,但由于其成员都有唯一的名称,匹配部分成员是可以不需要通配符,如下所示:
let filterPoint (points:Point list) =
points
|> List.filter
(function
| {x = 1.0} | {y = 1.0} –> true
| _ ->false)
filterPoint [mypoint1; mypoint2; mypoint3]
小结:
Records是F#的一种基本结构,用来定义简单的数据,使用起来也比较灵活而简单。
目前唯一不大习惯的是其创建方式,由于没有构造函数,成员不能使用缺省值,需要指定所有成员。这一点远没有C#创建对象并初始化成员那样来得灵活。并且由于其类型往往是靠推导出来的,创建的时候在VisualStudio中没有智能提示,一旦有一个成员名称拼写错误或类型不正确就有语法错误,这决定了其成员不宜过多,并且不宜使用过于复杂的名称,而C# 的IDE在这方面就要强大很多,基于其强大的智能提示,用C#写的代码基本上都是一次性成功,很少有语法错误。
posted @
2010-01-24 14:52 天方 阅读(22) |
评论 (0) |
编辑
Visual Studio 2010 Beta2早就发布了,但由于Visual Studio自带的设计器确实没法用,而Blend也不支持.net 4.0的wpf的工程,一直无缘体验一下新版的WPF。今天在微软的网站上发现了Microsoft Expression Blend Preview for .NET 4的下载。功能上基本上和blend3是一样的,没有任何变化,只是加了.net 4.0的工程支持。如果想体验新版wpf的功能的话可以试用一下。
另外需要注意的是:这个blend只支持.net 4.0工程,也就是说,它并不能代替blend3(应该是微软故意限制的),如果项目中混合了其它版本的工程,低版本的工程也是无法打开的(整个项目还是可以正常使用)。估计到Visual Studio 2010正式发布的时候才会推出一个全功能版的吧。
posted @
2010-01-24 02:07 天方 阅读(28) |
评论 (0) |
编辑