2012年4月4日
本此内容
本文首先介绍了该程序所能用到的WPF容器,然后布局界面提取图片。最后展示成果和提出下一步将要研究的问题。
1 WPF容器
2 布局界面
3 取文件夹图片
4 成果展示
5 总结和提问
1 WPF容器
容器是装对象的东西,本次讲解一类型可以排列对象的容器。WPF的Panel可以用来排列对象,我们把对象放入其中,WPF就能自动为之排列位置。System.Windows.Control.Panel是一个抽象类,所以我们不能直接使用<Panel/>的Xaml格式。其中Panel.Children是一个UIElementCollection,包含需要排列的各Item。Panel有不少派生类,我们可以使用这些Panel的派生类达到想要的排列效果。
StackPanel:Panel表示按照横向或纵向从左到右或从右到左的排列Item:任何Item都有一个排列的方式,如果没有特别指定,一般来说就是StackPanel的从上到下的压缩排列方式。若Item的长宽没有特别指定,则每个Item的高度将尽量的小,宽度将尽量的大,以占满整个Stack的宽度。若此时Stack的高度没有指定,则将尽量压缩。

WrapPanel:Panel排列Item从左到右,排列满了,然后换行,继续这样排列。WrapPanel可以根据Panel的宽度自动排列Item。

Grid:Panel是一种表格,功能强大。首先需要使用Grid.ColumnDefinitions, Grid.RowDefinitions定义表格的列属性和行属性。然后对其中的Item使用Grid.Row, Grid.Column定义其在该Grid中的第几行第几列,数值从0开始,即0行0列,就是实际上的1行1列。如果不指定几行几列则默认0行0列。下面是示例:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image Source="Images\1.jpg"/>
<Image Grid.Column="1" Source="Images\1.jpg"/>
<Image Grid.Row="1" Source="Images\1.jpg"/>
<Image Grid.Row="1" Grid.Column="1" Source="Images\1.jpg"/>
</Grid>
效果如下,一个田字格,如果没有设置行列则4张图将重叠在一起。

2 布局界面
我们使用Gird布局,上部是一个Button,此Button取得图片文件,然后显示在下部。下部使用WrapPanel。就像这样

代码如下:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button>取图片</Button>
<WrapPanel Grid.Row="1" ItemHeight="50" ItemWidth="50">
<Image Source="Images\1.jpg"/>
<Image Source="Images\2.jpg"/>
<Image Source="Images\3.jpg"/>
<Image Source="Images\4.jpg"/>
</WrapPanel>
</Grid>
|
Note:
- WrapPanel设置了ItemHeight和ItemWidth,如果不设置此2值,WrapPanel中的Item就会尽量扩大填满整个Panel,那时候你只能看到第1张图片了。
- Grid的RowDefinition我设置了Height=”auto”,如果不设置则两行Gird会尽量增高,以至于高度相等。设置auto使得第1行的高度随该行内容的高度而改变,第2行会尽量增高。
|
3 取得一文件夹图片
流程图:
取文件名集合
WPF可以响应Windows消息,我们在Button中添加Click消息:
<Button Click="Buton_Click1">取图片</Button>
产生事件处理函数:
void Button_Click1(object sender,RoutedEventArgs e){…}
使用FolderBrowserDialog选择文件夹。需要注意的是WPF并不提供此对话框。我们使用System.Windows.Forms中的组件。在使用之前做下面两件事情:
- 对此工程添加System.Windows.Forms引用
- 在用到的cs文件头写上using System.Windows.Forms
首先取得文件夹下的所有文件名,并剔除非图像文件。
var fileList = Directory.EnumerateFiles ( folderName );
string[ ] tokenList = { ".jpg", ".png", ".bmp", ".jpeg", "gif" };
var imageList = fileList.Where ( x => tokenList.Any ( x1 => x.EndsWith ( x1, StringComparison.InvariantCultureIgnoreCase ) ) );
var images1 = imageList.Select ( x => new Image { Source = new BitmapImage ( new Uri ( x ) ) } );
return images1.ToList ( );
Image类System.Windows.Controls空间中,在Xaml中表示为<Image>标签。然后我们把产生的Image加入Panel中。使用Panel.Children.Add方法。
internal void AddRange(IAddChild panel, IEnumerable itemList)
{
foreach (var item in itemList)
{
panel.AddChild(item);
}
}
响应按钮事件中添加此代码,用以显示图片组:
在Xaml中<WrapPanel Name="wrapPanel1">,然后程序会实例一个wrapPanel1供开发者使用。
void Button_Click1 ( object sender, RoutedEventArgs e )
{
FolderBrowserDialog dlg = new FolderBrowserDialog ( );
if ( dlg.ShowDialog ( ) == System.Windows.Forms.DialogResult.OK )
{
var imgs = new ImageGetter().GetList(dlg.SelectedPath);
wrapPanel1.Children.Clear();
new PanelManage().AddRange(wrapPanel1,imgs);
}
}
4 成果展示
图片的文件夹显示功能就完成了。


5 总结和提问
现在我们已经可以做出程序原型了。下一步我们将进行一些优化,并介绍新的控件。
PS1:其实此程序有两个隐患,大家拿回去编码运行试试。看能发现和修正不?
PS2:这也是我将进行优化的原因
2012年3月10日
目录:
引言
程序原型界面
WPF简介
新建一个WPF程序
WPF中标签和类的对应关系
载入图片
结语
引言
.Net体系有3个很重要的Foundation:WPF、WF、WCF,最近希望创建一个使用WPF的图像查看器,遂学习了相关知识,在实现该程序的过程中琢磨了大量的WPF知识,不敢独享。故写于此供各位大大参考和指点。
程序原型界面

初始想法就是这样,然后让我们开始做起来把,别看它貌似功能较少,但是用到的方法技巧可不简单。接下来我准备做一个系列,记录自己在做这个程序的时候的心得和学习历程,算是一个总结,也是抛砖引玉。
对于想学习WPF的朋友也可以跟着做,这样一来有了成果,二来也学到了知识,一举两得。本教程仅针对改图片浏览程序所用到的WPF知识进行了整理,挂一漏万,还请各位大大海涵和指教。
WPF简介
Windows的UI基础是User32和GDI/GDI+。然后有了C++向的MFC,C#向的WinForm,使用此API创建用户界面。DirectX的出现使得程序员可以使用各大厂商显卡的GPU创建更加Rich的应用程序界面。DirectX的API比较繁琐,一般程序的前端工程师不使用,且也用不着这么厉害的表示方式,除非是游戏界面,一般应用程序不需要用到。WPF(Windows Presentation Foundation)即能使用DirectX的丰富显示方式又能较容易的编码,它通过XAML代码实现而不是直接在cs文件里编辑,使得熟悉Html/Xml格式的开发人员更容易上手,而且WPF中的3D绘图和WebPage支持是一个很大的特色。
新建一个WPF程序
使用.Net4.0新建一个工程

App.xaml
<Application x:Class="WpfImage1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
App文件是程序的开始文件,注意App.xaml中的StartupUri="MainWindow.xaml",此属性表示程序会New一个MainWindow.xaml表示的窗口,作为起始窗口。
MainWindow.Xaml
<Window x:Class="WpfImage1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
</Grid>
</Window>
WPF和类的对应关系
一般地,一个WPF的标签表示一个类(class),标签里面的属性表示该类的属性(Property),比如上面的代码就表示一个Window类,其中属性Title=””MainWindow” Height=”350” Width=”525”,这些属性不一定非要在Xaml设置,在CS文件中也能直接对这些属性赋值。
就像这样
Window wd=new Window();
wd.Title=””MainWindow2”;
wd.Height=”350”;
wd.Width=”525”;
wd.Show();
把这段代码加入MainWindow的构造函数中就能创建一个标题为””MainWindow2”的窗口了。

其中Window标签中的<Grid>也是一个类,不过作为Window的子元素只能有一个。Window类继承自ContentControl类,ContentControl.Content约束只能有一个Content。
载入图片
既然是图片浏览器,那么就需要可以载入图片。我们使用标签<Image>,像这样加入MainWindow.xaml的Grid标签中
<Grid>
<Image Source="..."></Image>
</Grid>
Image标签可以设置图片的Width/Height和拉伸方式Stretch。重要的是其中的Source属性,在此属性中放入图片路径比如”C:\1.jpg”就可以显示一张图片了。
结语
首先提出了课程目标,然后讲解了WPF的相关知识,并学会使用<Image>标签显示图片。
下一节我们将开始WPF的Panel类型的讲解,并选择合适的Panel作为图片处理程序的载体。
谢谢 :-)
2012年2月4日
先看一段代码
static void Main ( )
{
var test1 = new Test1();
test1.Property1=3;
Console.ReadKey ( );
}
class Test1
{
private int Property1 { get; set; }
}
test1.Property1=3;必定编译错误。private有访问级别的限制。
代码修改后
static void Main ( )
{
var test1 = new Test1();
Type type1=test1.GetType();
PropertyInfo pi=type1.GetProperty("Property1", BindingFlags.Instance | BindingFlags.NonPublic);
pi.SetValue(test1,3,null);
test1.Show();
Console.ReadKey ( );
}
class Test1
{
private int Property1 { get; set; }
public Show(){
Console.WriteLine(Property1);
}
}
使用反射就能成功的修改test1中的私有Property1。在反射面前private的保护失效。
不过如果真的需要访问Private数据,还是调用那个类提供的对应Set函数,比如这样:
class Test1
{
public int Property1 { set{
field=value;
}}
private int field;
public Show(){
Console.WriteLine(field);
}
}
这种使用反射的使用方式破坏了类的封装性,最好不要用。
2011年8月14日
最近编码时遇到了一个问题,先看一个程序:
static void Main ( string[ ] args )
{
MyValueType mv = new MyValueType ( b: 2.3 );
object objMv = mv;
Console.WriteLine ( mv );
Console.WriteLine ( objMv );
/* Step1 */
( (MyValueType) objMv ).Method1 ( 3.4 );
Console.WriteLine ( objMv );
/* Step2 */
( (IMyValueType) mv ).Method1 ( 4.5 );
Console.WriteLine ( mv );
/* Step3 */
( (IMyValueType) objMv ).Method1 ( 5.6 );
Console.WriteLine ( objMv );
Console.ReadKey ( true);
}
struct MyValueType:IMyValueType{
public int a;
public double b;
public bool c;
public MyValueType ( int a=default(int),
double b=default(double),bool c=default(bool)) {
this.a=a;
this.b = b;
this.c = c;
}
public override string ToString ( )
{
return string.Format ( "a={0},b={1},c={2}", a, b,c );
//return base.ToString ( );
}
public void Method1 ( double b = default(double) )
{
this.b = b;
}
}
interface IMyValueType {
void Method1 ( double b = default(double) );
}
问:上述3个步骤的控制台输出是什么?(主要看b的值)
各位先猜猜b的值在3个步骤中输出是什么,然后复制粘贴运行下,看看是不是答对了!答对了的童鞋也请想想为什么会是这样的结果。
下面引出主题
Struct(值类型)的装箱和拆箱
一、预备知识
1.1 继承
所有值类型都是sealed的,即不能被继承。struct不能继承其它类型(其实已经继承自ValueType类了),不过可以实现多个接口。

1.2 初始化
struct中的每个Field在其默认构造函数中都初始化为default(xxx)类型的值,C#完全禁止了用户显示定义默认构造器,也不能在声明时对Field进行赋值。如果要自己编写构造函数,则必须对每个Field赋值,不能只对其中一些Field赋值。
1.3 存储
struct和class不同,数据存储在栈上,而不是堆上。
二、与引用类型的转换
2.1 装箱步骤(值类型转换为引用类型)
1) 在堆中分配对应的内存空间
2) 内存复制操作,栈上的数据复制到堆中
3) 更新对象或接口引用,使之指向堆中的位置
装箱操作往往是隐式转换中用到的。
2.2拆箱操作(引用类型转换为值类型)
拆箱会解除对堆中对象的引用。一般地,拆箱完毕后会有复制内存的操作。拆箱操作必须显式转换。另外,必须拆箱为其基础类型,见下面代码:
int ia;
object ob;
double dc;
ia=23;
ob=ia;(装箱操作)
/*InvalidCastException,拆箱操作只能返回int类型,应确保源类型能转换成目标类型*/
//dc=(double)ob;
dc=(double)(int)ob;//正确,要首先拆箱为基础类型
三、开头代码的解释
第一步以前:
object objMv = mv;
该步骤引发装箱操作,会在堆上产生一个mv的内存副本,然后objMv指向该副本
第一步:
objMv要调用struct的Method1函数,就必须拆箱为struct。这样,就会产生一个栈上的临时副本,函数Method1对临时副本进行了更改,但是不会被保存在堆上的objMv中。所以堆上的objMv的b值不会改变
第二步:
mv要调用接口IMyValueType的函数Method1,就会被装箱为IMyValueType。这样,就会产生一个堆上的临时副本,函数Method1对临时副本进行了更改,但是不会影响栈上mv的内存数据。
第三步:
引用类型之间的转换,没有装拆箱操作,不会有“复制”操作,即Method1可以更改objMv中的值
四、总结
装箱、拆箱有一些容易被人忽视的特性。就像开头代码一样,以为修改了struct或object的值,其实根本没有修改,从而引发致命错误。记住“不要创建可变值类型”,就会在很大程度上避免其中的多数问题。
Ps:有什么不足之处多多指教哈,谢谢~J
2011年7月30日
在C#中,我们可以使用is as操作符来判断某数据类型和其它指定类型的关系
1. 用is运算符验证基础类型
C#允许在继承链中向下转型,所以如果DerivedClass派生自BaseClass,那么DerivedClass a; a is BaseClass返回true
2. 用as而不是强制转换
as运算符在某些情况下能代替强制转换操作,我们推荐使用as而不是以前的强制转换,因为普通的强制转换如果失败,会抛出一个需要处理的异常;但是as转换如果失败,仅是把转换结果变为null,而不是抛出一个异常。
3. as是is的语法糖
下面两段代码是一个效果
1 Foo f = x as Foo;
2
3 var temp = x;
4 Foo f = (temp is Foo) ? (Foo)temp : (Foo)null;
具体参见Eric大大的blog
http://blogs.msdn.com/b/ericlippert/archive/2010/09/16/is-is-as-or-is-as-is.aspx
4. as 不是万能转换器
例如:用户自定义转换
1 ClassA{
2
3 public static explicit operator ClassB (ClassA a){
4
5 return new ClassB();
6
7 }
8
9 }
10
11 ClassA a=new ClassA();
12
13 ClassB b;
14
15 b=(ClassA)a;//正确。通过自定义转换
16
17 b=a as ClassB;//返回null。is判断就不会通过
as运算符只执行引用转换和装箱转换。
as运算符无法执行其他转换,如用户定义的转换,这类转换应使用case表达式来代替其执行。
虽说as是is的语法糖,但是is能够用于不可为空类型判断,而as就不能。
"a as int;"这样的代码会错误,除非是“a as int?;”,“ a is int”这样的代码就是没问题的
int a=3;
Console.WriteLine ( a is int );//正常判断,输出true
int? b = a as int?;//正常转换
int c = a as int;//报错:as不能用于不能为null的类型转换
5. 总结
1. as是is的语法糖,即as是否能正确转换,内部还是靠is的验证的。
1.1. 虽说是语法糖,但是is能作用于不可为空类型(例如:值类型),as不能作用于此类型
2. as比强制转换能好,毕竟转换失败它不会抛出异常。
3. as不是万能的,例如强制转换能够处理“用户自定义转换”,而as就不行了。
6. 鸣谢
语法糖理论:@,@,@
as不能作用于不能为空的类型转换,而is就可以:@
提出编码错误:@,@
实践出真知:@
及各位耐心阅读并提出意见的朋友 :)
还有其它不正确的地方请多多指教 !
2011年7月17日
摘要: dynamic介绍:前言:最近公司某项目中类型定义不能在编译期间确定,表结构为动态可变类型。有参考C#4.0最新特性,故成此作。1.dynamic和vardynamic是C#4.0新增关键字,和以前动态确定类型的关键字var还是有所不同,以下是区别dynamicvar类型确定时机可推迟到运行期确定数据类型在编译期必须确定数据类型类型改变与否同一dynamic参数可以,针对不同的类型编译期一旦确定了参数类型,则不能改变了函数声明函数声明时参数dynamic是可用的函数声明参数不能使用var总结:var类型修饰的参数需要能在编译时确定参数类型,而dynamic可推迟到运行时确定;如果var不能确定
阅读全文