C#小知识(篇章一)

一.枚举类型数据绑定

1.经典方法

没有直接方法可以将枚举作为绑定的数据源. Enum.GetValues(Type) 方法可返回值的集合,这些值可以包装在 ObjectDataProvider 中并用作数据源。ObjectDataProvider 类型提供了一种在 XAML 中创建对象并将其用作数据源的便捷方式。
(1).创建一个枚举

// An highlighted block
public enum FrequencyEnum
    {
        F2_5US = 0,
        F5US = 1,
        F10US = 2,
        F20US = 3,
        F50US = 4,
        F100US = 5,
        F200US = 6,
        F500US = 7,
        F1MS = 8,
    } ;

(2).添加引用
在MainWindow.xaml文件中从mscorlib中引入命名空间System

xmlns:sys="clr-namespace:System;assembly=mscorlib"

(3).添加枚举类型的命名空间

xmlns:model="clr-namespace.General.UI.Model;assembly=General.UI"

(4).在应用程序 XAML 或正在使用的对象的 XAML 中创建一个新的 ObjectDataProvider 作为 XAML 资源。 此示例使用一个窗口并使用资源键 DataFromEnum创建 ObjectDataProvider。ObjectDataProvider 使用三个属性来检索枚举:
ObjectType :数据提供程序要返回的对象类型。 在本示例中为 System.Enum。 sys: XAML 命名空间映射到 System。
MethodName :要在 System.Enum 类型上运行的方法的名称。 在本示例中为 Enum.GetValues。
MethodParameters : 要提供给 MethodName 方法的值的集合。 在此示例中,该方法采用枚举的 System.Type。

// An highlighted block
<UserControl.Resources>
        <ObjectDataProvider  x:Key="DataFromEnum"
                             MethodName="GetValues"
                             ObjectType="{x:Type sys:Enum}">
            <ObjectDataProvider.MethodParameters>
                <!--枚举位置和名字-->
                <x:Type TypeName="enumref:FrequencyEnum"/>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
</UserControl.Resources>

实际上,XAML 正在分解方法调用、方法名称、参数和返回类型。 上一示例中配置的 ObjectDataProvider 等效于以下代码:

var DataFromEnum = System.Enum.GetValues(typeof(System.Windows.FrequencyEnum));

(5).引用 ObjectDataProvider 资源。 以下 XAML 列出了 ListBox 控件中的枚举值和Combox控件中的1枚举:

<ListBox Name="myComboBox" SelectedIndex="0"
         ItemsSource="{Binding Source={StaticResource DataFromEnum}}"/>
<ComboBox   HorizontalAlignment="Center"
            VerticalAlignment="Center"
            ItemsSource="{Binding Source={StaticResource DataFromEnum}}">

二.模式选择+关联选框

当要控制一个窗口不同操作下需要隐藏时候和combox关联的时候,当上一个选框变动下一个会跟着变动。

<ComboBox Width="Auto" Grid.Row="5" Grid.Column="1" >
      <ComboBox.Style>
           <Style TargetType="ComboBox" 
                  BasedOn="{StaticResource ComboBoxBaseStyle}">
               <Setter Property="ItemsSource"
                   Value="{Binding Source={StaticResource FilterParamCOFFKey}}" />
               <Setter Property="SelectedItem" 
                   Value="{Binding SetFilterParamCOFF_LK}"/>
                <Setter Property="Visibility" Value="Collapsed"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding SetFilterMode_LK}" 
                              Value="MOVING_AVERAGE">
                         <Setter Property="ItemsSource" 
                            Value="{Binding Source={StaticResource FilterParamAVEKey}}" />
                         <Setter  Property="SelectedItem" 
                            Value="{Binding SetFilterParamAVE_LK}"/>
                     </DataTrigger>
                     <DataTrigger Binding="{Binding CurrentSelector}" Value="LK">
                         <Setter Property="Visibility" Value="Visible" />
                  </DataTrigger>
                </Style.Triggers>
            </Style>
         </ComboBox.Style>
 </ComboBox>

三.as用法的详讲

1.as类型转换和强制转换的区别

(1)C#强制类型转换方法;

Object obj1 = new NewType();
NewType newValue = (NewType)obj1

强制转换是不安全的,需要用try-catch语句进行保护

Object obj1 = new NewType();
NewType newValue = null;
try
{
    newValue = (NewType)obj1;
}
catch (Exception err)
{
    MessageBox.Show(err.Message);
}

(2)as转换方法;

Object obj1 = new NewType();
NewTYpe newValue = obj1 as NewType;

安全性:
as操作符不会做过的转换操作,当需要转化对象的类型属于转换目标类型或者转换目标类型的派生类型时,那么此转换操作才能成功,而且并不产生新的对象【当不成功的时候,会返回null】。因此用as进行类型转换是安全的。
效率:
当用as操作符进行类型转换的时候,首先判断当前对象的类型,当类型满足要求后才进行转换,而强制转换的方式,是用当前对象直接去转换,而且为了保护转换成功,要加上try-catch,所以,相对来说,as效率高点。
注意:不管是强制转换还是as操作符进行类型转换,在使用之前,需要进行判断转换是否成功

if(newValue != null{
  //Work with the object named “newValue“
}

2.as操作符注意事项

(1)不用在类型之间进行类型转化;

NewType newValue = new NewType();
NewTYpe1 newValue = newValue as NewTYpe1;

可以用传统的类型转换方式完成:

NewTypeOne newTestOne = new NewTypeOne();
NewTypeTwo newTestTwo = (NewTypeTwo) newTestOne;

要想使上面的操作正确完成,也可以在原有类型中增加类型转换操作符函数;

public calss NewTypeOne
{
  public static explicit operator NewTypeTwo( NewTypeOne obj1)
  {
  //Convert object into new type
  }
}

(2)不能应用在值类型数据;

Object obj1 = 11;
int nValue = obj1 as int;

可以使用is操作符,再加上强制类型操作,就可以安全完成转换;

Object obj1 = 11;
if(objTest is int )
{
  int nValue = (int) obj;
}

3.C#中提供的很好的类型转换方式总结

Object => 已知引用类型——使用as操作符完成;
Object => 已知值类型——先使用is操作符来进行判断,再用类型强转换方式进行转换;
已知引用类型之间转换——首先需要相应类型提供转换函数,再用类型强转换方式进行转换;
已知值类型之间转换——最好使用系统提供的Conver类所涉及的静态方法。

4.as与is

(1)is
检查对象是否与给定的类型兼容。

if(obj is MyObject)
{
  //is
}

可以确定MyObject类型的一个实例,或者对象是否是从MyObject派生的一个类型。如果所提供的表达式非空,并且所提供的对象可以强制转换为所提供的类型而不会导致引发异常,则 is 表达式的计算结果将是 true。 如果已知表达式始终是true或始终是false,则is关键字将导致编译时警告,但是通常在运行时才计算类型兼容性。
注意:
①is运行符不能重载,
②is运行符只考虑引用转换、装箱转换和取消装箱转换。
③不考虑其它转换,如果用户定义转换,在Is运算符的左侧不允许使用匿名方法。lambda表达式属于

Object myObject = new Object(); 
 Boolean b1 = (myObject is Object);      true.  
 Boolean b2 = (myObject is Employee);    false.

如果对象引用是null,is运算符总是返回false,因为没有可检查其类型的对象。

  if (myObject is Employee)
  {
      Employee myEmployee = (Employee)myObject;
  }

在这上面段代码中,CLR实际会检查两次对象的类型。
①is运算符首先核实myObject是否兼容于Employee类型。如果是,那么在if语句内部执行转换型,
②CLR会再次核实myObject是否引用一个Employee。
CLR的类型检查增加了安全性,但这样对性能造成一定影响。这是因为CLR首先必须判断变量(myObject)引用的对象的实际类型。然后,CLR必须遍历继承层次结构,用每个基类型去核对指定的类型(Employee)。由于这是一个相当常用的编程模式,所以C#专门提供了as运算符,目的就是简化这种代码写法,同时提升性能。
(3)区别
as:用于检查在兼容的引用类型之间执行某些类型的转换

  Employee myEmployee = myObject as Employee;
  if (myEmployee != null)
  {
  }

as运算符造成CLR只校验一次对象的类型,CLR核实myObject是否兼容于Employee类型;如果是,as会返回对同一个对象的一个非null 的引用。如果myObject不兼容于Employee类型,as运算符会返回null。if语句只是检查myEmployee是否为null。这个检查的速度比校验对象的类型快得多。
as运算符的工作方式与强制类型转换一样,只是它永远不会抛出一个异常。相反,如果对象不能转换,结果就是null。所以,正确的做法是检查最终生成的一引用是否为null。如果企图直接使用最终生成的引用,会抛出一个System.NullReferenceException异常。以下代码对此进行了演示:

 Object o = new Object();        新建一个Object对象。
 Employee e = o as Employee;     将o转型为一个Employee
 e.ToString();                   访问e会抛出一个NullReferenceException异常

as运算符类似于强制转换操作。但是无法进行转换,则as返回null而非引发异常。

5.总结

expression as Type它等效于以下表达式,但是只计算一次expression。
expression is Type ?(Type)expression : (Type)null
as运算符只执行引用转换和装箱转换。as运算符无法执行其它转换,如果用户定义的转换,这类转换应使用强制转换表达式来执行。

三.委托,Lambda表达式,事件

1.委托

1.1 知识介绍

委托是寻址方法,函数指针是一个指向内存位置的指针,类型不安全,无法发判断实际的指向,参数和返回值更无法知晓。委托是类型安全的类,他定义了返回类型和参数类型。委托不仅包含了对方法的引用,还包含了对多个方法的引用。lambda表达式与委托直接相关,当参数是委托类型是,就可以使用lambda表达式实现委托用的方法。
委托是一个特殊类型的对象,以前定义的所有对象都包含数据,而委托包含的只是一个或者多个方法的地址。

private delegate string GetString();
static void Main(string[] args)
{
      int x = 10;
  //  GetString getString = new GetString(x.ToString);
 //   Console.WriteLine("String is {0}",getString());
 //   Console.WriteLine("String is {0}",x.ToString());
      GetString getString = x.ToString;
      Console.WriteLine("String is{0}", getString());
 }

不能将带有()方法带,传给委托变量。这里是把方法的地址赋予委托变量,并不是方法的结果。
委托的实例可以引用任何类型的任何对象上的实例方法或静态方法,只要方法签名匹配即可。

1.2 Action< T >和Func<int T,out OutT>

Action< T > 引用一个viod返回类型的方法,多个参数可以写成Action<var T1,var T2,var T3>等价于delegate void delegateName(var T1,var T2,var T3);
Func<int T,out OutT>带有返回值类型的最后一个参数是返回值。
数组实现方式

 private delegate string GetString();
 private delegate double DoubleOp(double x);
 
 static void Main(string[] args)
 {
         
      DoubleOp[] doubleOp = {
           MathOp.MulOp,
           MathOp.SquOp,
      };
            
      for (int i = 0; i < doubleOp.Length; i++)
      {
          Pross(doubleOp[i], 1);
          Pross(doubleOp[i], 3);
      }

      static void Pross(DoubleOp action, double value)
       {
           double result = action(value);
           Console.WriteLine("Value is {0},result is {1}", value, result);
       }
}
class MathOp
{
     public static void ShowOne(double value)
     {
          Console.WriteLine("One is {0}", value);
     }

     public static void ShowTwo(double value)
     {
         Console.WriteLine("Two is {0}", value);
     }
}

Func实现

     static void Main(string[] args)
      {
          Func<double, double>[] doubleOp = {
                MathOp.MulOp,
                MathOp.SquOp,
            };

            for (int i = 0; i < doubleOp.Length; i++)
            {
                Pross(doubleOp[i], 1);
                Pross(doubleOp[i], 3);
            }



            static void Pross(Func<double,double> action, double value)
            {
                double result = action(value);
               Console.WriteLine("Value is {0},result is {1}", value, result);
            }
        }
  class MathOp
  {
        public static double MulOp(double value)
        {
            return value * 2;
        }

        public static double SquOp(double value)
        {
            return value * value;
        }

        public static void ShowOne(double value)
        {
            Console.WriteLine("One is {0}", value);
        }

        public static void ShowTwo(double value)
        {
            Console.WriteLine("Two is {0}", value);
         }

  }

1.3 多播委托

可以按照顺序调用多个方法,为此签名必须返回void,不然就只能得到最后一个调用的结果。
多播委托可以识别+和+=来时扩充调用的方法,也可以用-和-=来删除方法调用。其实就是将多个方法链接为一个列表。

 static void Main(string[] args)
        {
            Action<double> ops = MathOp.ShowOne;
            ops += MathOp.ShowTwo;

            ops(1);
        }
 class MathOp
    {
        public static double MulOp(double value)
        {
            return value * 2;
        }

        public static double SquOp(double value)
        {
            return value * value;
        }

    }

结果
One is 1
Two is 1
值得注意的是多播委托是一个逐个调用的委托集合,如果中间一个方法抛出异常,整个迭代就会停止。为了避免这种问题应自己迭代方法列表,Delegate定义一个GetInvocationList()方法,它返回一个Delegate对象数组,这样遇到异常也可以进行下次层迭代。

void one()
{
throw new Exception("Error");
}
void two()
{
}
void main()
{
	Action d=one;
	d+=two;
	
	Delegate[] delegates=d.GetinvocationList();
	foreach(Action d int delegates){
	     try
	     {
	         d();
	     }
	     catch(Exception)
 	     {
	         Console.WriteLine("Exception caught");
	     }
	}
}
//这里当运行到one就会停止
//自己写一个迭代列表就可以了
void main()
{
	Action d=one;
	d+=two;
	try
	{
	    d();
	}
	catch(Exception)
	{
	     Console.WriteLine("Exception caught");
	}
}

1.4 匿名方法

Func<string,string> annonDel=delegate(string param)
{
	param+="Hello";
	param+="Word";
	return param;
};

匿名方法有两个原则:
1.不能使用跳转语句(break,goto,continue)
2.不能访问不完全的代码,不能使用ref,out修饰参数

1.5协变和逆变

向委托分配方法时,协变和逆变为匹配委托类型和方法签名提供了灵活性。
协变:允许方法具有的派生返回类型多于委托中定义的类型。

class Mammals {}  
class Dogs : Mammals {}  
  
class Program  
{  
    // Define the delegate.  
    public delegate Mammals HandlerMethod();  
  
    public static Mammals MammalsHandler()  
    {  
        return null;  
    }  
  
    public static Dogs DogsHandler()  
    {  
        return null;  
    }  
  
    static void Test()  
    {  
        HandlerMethod handlerMammals = MammalsHandler;    
        HandlerMethod handlerDogs = DogsHandler;  //协变两个返回值不一样
    }  
}

逆变:允许方法具有的派生参数类型少于委托类型中的类型。

public delegate void KeyEventHandler(object sender, KeyEventArgs e);
public delegate void MouseEventHandler(object sender, MouseEventArgs e);

该示例定义了一个具有 EventArgs 参数的事件处理程序,并使用它来处理 Button.KeyDown 和 Button.MouseClick 事件。 它可以这样做是因为 EventArgs 是 KeyEventArgs 和 MouseEventArgs 的基类型。

private void MultiHandler(object sender, System.EventArgs e)  //接受EventArgs类型参数的事件处理程序。
{  
    label1.Text = System.DateTime.Now.ToString();  
}  
  
public Form1()  
{  
    InitializeComponent();  
    
    this.button1.KeyDown += this.MultiHandler;  //可以使用一个有EventArgs参数的方法,尽管事件是KeyEventArgs参数。
    this.button1.MouseClick += this.MultiHandler;  //可以使用相同的方法,处理MouseEventArgs参数的事件。
  
}

1.6Delegates与delegate区别

Delegate是个类,基类,抽象类。
delegate是一个关键字,
类不是委托类型,该类用于派生委托类型。

1.7其他
名词介绍:
委托判断:为了减少输入量,只要需要委托实例,就可以只传送地址的名称。
firstMode();
firstMode.Invoke();//完整版
帮助文档:
1.delegates
2.Delegates
3.System.Delegate 和 delegate 关键字

2.lambda表达式

2.1介绍
只要有委托就可以用lambda表达式,上面的代码可以改成:

Func<string,string> lambda = param =>
{
	param+="Hello";
	param+="Word";
	return param;
};

param是参数,如果操作只有一行return可以省略,多行就必须有return。
多参数的可以写成:

Func<double,double,double> lambda=(x,y)=>x*y;

可以在括号内加上参数类型,参数可以帮助找到匹配委托。(double x,double y)
2.2闭包
闭包,就是通过lambda表达式可以访问表达式块外部的变量。

int someVal=5;
Func<int,int> f=x=>x+somVal;

编译过程中,会创造一个匿名类,他的构造函数来传递外部变量,该构造函数取决于外部传递过来的数量。

//底层实际代码
public class AnonymousClass
{
	public int someVal;
	public AnonymousClass(int someVal)
	{
		this.someVal=someVal;
	}
	public int AnonmousMethod(int x)
	{
		return x+someVal;
	}
}

2.3foreach语句的闭包

val value =new List<int>() {10,20,30};
var funcs=new List<Func<int>>();

foreach(var val in value)
{
	funcs.Add(()=>val);
}

foreach(var f in funcs)
{
	Console.WriteLine(f());
}

3.事件

1.1介绍
事件基于委托,为委托提供一个发布/订阅机制。
列举实例1:书上的例子可能有点不好懂
(1).事件发布程序

//建立一个车类的,继承EventArgs
   public class CarInfoEventArgs : EventArgs
    {

        public string Car { get; private set; }
        public CarInfoEventArgs(string car)
        {
            this.Car = car;
        }
    }

    public class CarDealer //新车到达时候触发事件
    {
         public event EventHandler<CarInfoEventArgs> NewCarInfo;
        //事件一般带有两个参数的方法,第一个参数是一个对象,包括事件的发送者,第二个参数提供了事件的相关信息。
        //Event<TEventArgs>,他的返回void,两个参数,第一个是object类,第二个是T类型,T类型必须派生基类EventArgs

        public void NewCar(string car)
        {
            Console.WriteLine("CarDealer,new car {0}", car);
            RaiseNewCarInfo(car);
        }

        protected virtual void RaiseNewCarInfo(string car)//判断事件是否为空
        {
            EventHandler<CarInfoEventArgs> newCarInfo = NewCarInfo;
            if (newCarInfo != null)
            {
                newCarInfo(this, new CarInfoEventArgs(car));
            }
        } 
    }

EventHandler,他的返回void,两个参数,第一个参数是一个对象,包括事件的发送者,是object类,第二个参数提供了事件的相关信息,是T类型,T类型必须派生基类EventArgs

(2)事件侦听器(订阅)

public class Consumer
    {
        private string name;

        public Consumer(string name) 
        {
            this.name = name;
        }

        public void NewCarIsHere(object sender, CarInfoEventArgs e) 
        {
            Console.WriteLine("{0} :car {1} is new", name, e.Car);
        }

    }

(3)main()

 static void Main(string[] args)
  {
      var dealer = new CarDealer();

      var minchael = new Consumer("One");
      dealer.NewCarInfo += minchael.NewCarIsHere;

      dealer.NewCar("Ferrari");

      var minchael1 = new Consumer("Two");
      dealer.NewCarInfo += minchael1.NewCarIsHere;

      dealer.NewCar("Ferrari");
 }

(4).结果
CarDealer,new car Ferrari
One :car Ferrari is new
CarDealer,new car Ferrari
One :car Ferrari is new
Two :car Ferrari is new

实例2:
一个关于打工仔的例子,发布者,发布他可以工作了,订阅者,则是让他做的事情。

(1).发布可以做了

delegate void ToolDelegate();
    class ToolClass
    {
        public event ToolDelegate toolDelegate = null;

        public string ToolName { get; private set; }

        public ToolClass(string name)
        {
            this.ToolName = name;
        }


        public void Dothing() 
        {
            Console.WriteLine("Doing.....");
            if (toolDelegate != null)
            {
                //toolDelegate();
                Delegate[] delegates = toolDelegate.GetInvocationList();
                foreach(ToolDelegate d in delegates)
                {
                    try
                    {
                        d();
                    }
                    catch 
                    {
                        throw new Exception("ERROR");
                    }
                    
                }
            }
            else 
            {
                throw new Exception("Delegate is null");
            }

        }


    }

(2)订阅可以做哪些事情

class RealClass
    {
        public string RealName { get; private set; }

        public RealClass(string name) 
        {
            this.RealName = name;
        }

        public void DoTest() 
        {
            Console.WriteLine("{0} DoTesting",RealName);
        }

        public void DoRun() 
        {
            Console.WriteLine("{0} DoRuning",RealName);
        }

    }

(3)main

static void Main(string[] args)
        {

           ToolClass toolClass = new ToolClass("OneTool");

            RealClass realClass1 = new RealClass("OneReal");
            RealClass realClass2 = new RealClass("TwoReal");
            RealClass realClass3 = new RealClass("ThreeReal");

            toolClass.toolDelegate += realClass1.DoRun;
            toolClass.toolDelegate += realClass2.DoTest;
            toolClass.toolDelegate += realClass3.DoRun;

            toolClass.Dothing();

          //  toolClass.toolDelegate();
        }

public event ToolDelegate toolDelegate = null; 这个语句加event与没event区别,有的时候,不可以直接调用 toolClass.toolDelegate();而且方法只可以用+= 和-=,不可以用==。

3.2弱事件

有一个问题;如果侦听器不在直接引用,发布程序就仍会有一个引用,垃圾回收器不能清空侦听器占用的内存。也就是说,当订阅器离开作用域(订阅不在需要时)之前,要确保取消对事件的订阅。
弱事件:就是将发布程序和侦听器不在强连接了,当不在引用侦听器时,它就会被垃圾回收。

(1).弱事件管理器
弱事件需要一个事件事件管理器,用来管理发布程序和侦听器之间的连接。

//弱事件管理器类,管理NewCarInfo事件的发布程序和侦听器之间的连接。
    public class WeakCarInfoEventManager : WeakEventManager
    {
        //弱事件管理器需要静态方法AddListener和RemoverListener,侦听器使用这些方法连接发布程序,断开与发布程序的连接。
        public static void AddListener(object source, IWeakEventListener listener) 
        {
            CurrentManager.ProtectedAddListener(source, listener);// ProtectedAddListener 将提供的侦听器添加到被管理的事件提供的源。
        }
        public static void RemoveListener(object source, IWeakEventListener listener) 
        {
            CurrentManager.ProtectedRemoveListener(source, listener);
        }


        public static WeakCarInfoEventManager CurrentManager //用于访问WeakCarInfoEventManager的单态对象
        {
            get 
            {
                var manager = GetCurrentManager(typeof(WeakCarInfoEventManager)) as WeakCarInfoEventManager;

                if (manager == null) 
                {
                    manager = new WeakCarInfoEventManager();
                    SetCurrentManager(typeof(WeakCarInfoEventManager), manager);
                }

                return manager;
            }
        }

        //添加第一个侦听器调用
        protected override void StartListening(object source)
        {
            (source as CarDealer).NewCarInfo += CarDealer_NewCarInfo;
        }

        //删除最后一个侦听器调用
        protected override void StopListening(object source)
        {
            (source as CarDealer).NewCarInfo += CarDealer_NewCarInfo;
        }

        void CarDealer_NewCarInfo(object sender, CarInfoEventArgs e) 
        {
            DeliverEvent(sender, e);//DeliverEvent,托管代码流向每个侦听器的事件传送。
        }

    }

(2)发布程序
代码不变
(3)侦听器
改成管理器调用方式,继承IWeakEventListener,实现ReceiveWeakEvent

public class Consumer:IWeakEventListener
    {
        private string name;

        public Consumer(string name) 
        {
            this.name = name;
        }

        public void NewCarIsHere(object sender, CarInfoEventArgs e) 
        {
            Console.WriteLine("{0} :car {1} is new", name, e.Car);
        }

        public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            NewCarIsHere(sender, e as CarInfoEventArgs);
            return true;
        }
    }

(4)main

 static void Main(string[] args)
        {
            var dealer = new CarDealer();

            var minchael = new Consumer("One");
            WeakCarInfoEventManager.AddListener(dealer, minchael);
            dealer.NewCar("FerrariOne");

            var minchael1 = new Consumer("Two");
            WeakCarInfoEventManager.AddListener(dealer, minchael1);
            dealer.NewCar("FerrariTwo");

        }

3.3泛型弱事件管理器

泛型WeakEventManager<TEventSource,TEventArgs>派生自基类WeakEventMananger简化弱事件的处理。使用这个不要在定义弱事件管理器,也不需要让事件的消费者去实现IWeakEventsListener接口。
(1).main

static void Main(string[] args)
 {
       CarDealer dealer = new CarDealer();
       Consumer micheal = new Consumer("Micheal");

        WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", micheal.NewCarIsHere);
        dealer.NewCar("DealerOne");
 }

(2)发布不变
(3)订阅把IWeakEventsListener不要

3.4验证弱事件

验证一下,垃圾回收的问题,当Mincheal不需要时,会不会回收。

  CarDealer dealer = new CarDealer();
  Consumer micheal = new Consumer("Micheal");
  dealer.NewCarInfo += micheal.NewCarIsHere; 
  dealer.NewCar("DealerOne");
  micheal = null;
  GC.Collect();
  dealer.NewCar("DealerTwo");
  Console.ReadLine();

运行结果:
CarDealer,new car DealerOne
Micheal :car DealerOne is new
CarDealer,new car DealerTwo
Micheal :car DealerTwo is new
很明显输出了Micheal :car DealerTwo is new,订阅事件并没有得到处理。

  CarDealer dealer = new CarDealer();
  Consumer micheal = new Consumer("Micheal");
  WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", micheal.NewCarIsHere);
  dealer.NewCar("DealerOne");
  micheal = null;
  GC.Collect();
  dealer.NewCar("DealerTwo");
  Console.ReadLine();

运行结果:
CarDealer,new car DealerOne
Micheal :car DealerOne is new
CarDealer,new car DealerTwo

3.3事件与委托的区别和联系

  1. 事件是一种特殊的委托,或者说是受限制的委托,是委托一种特殊应用,只能施加+=,-=操作符。二者本质上是一个东西。
  2. event ActionHand ler Tick ;//编译成创建一个私有的委托示例,和施加在其上的 add , remove 方法.
  3. event 只允许用 add , remove 方法来操作,这导致了它不允许在类的外部被直接触发,只能在类的内部适合的时机触发。委托可以在外部被触发,但是别这么用。
  4. 使用中,委托常用来表达回调,事件表达外发的接口。
  5. 委托和事件支持静态方法和成员方法, delegate ( void * pthis , f _ ptr ),支持静态返方法时, pthis 传 null .支持成员方法时, pthis 传被通知的对象,
  6. 委托对象里的三个重要字段是, pthis , f _ ptr , pnext ,也就是被通知对象引用,函数指针/地址,委托链表的下一个委托节点。

四.任务,线程,同步

五.DataGrid动态生成列(待补充)

1.介绍
通常情况下,DataGrid动态生成列通过绑定DataTable类型的数据源实现。此方法很适用于数据库单表查询并且表结构为横表形式,倘若数据结构存在嵌套或者表结构为竖表形式,则需要做很多工作进行处理。在WPF中,主要使用DataGrid控件进行表格化数据的展示,控件提供一个用户界面,用于 ADO.NET 数据集(ADO.NET是微软的一个组件库,作为数据访问接口使用,详细内容直接参考百科ADO.NET),并显示表格数据和启用数据源更新。DataGrid 控件设置为有效数据源时,则自动填充该控件,同时根据数据的形状创建列和行。 DataGrid 控件可用于显示单个表或显示一组表之间的分层关系。

2.实例一

以下从List类型的行数据入手,并且object可以为任意自定义数据结构,来探究如何进行列的生成与数据绑定。

(1).ViewModel包含数据源

自定义一个数据结构类

    public class Factor
    {
        public string Name { get; set; }
        public string Min { get; set; }
        public string Avg { get; set; }
        public string Max { get; set; }
        public string Flag { get; set; }
    }

假设每行数据由DataTime和N个Factor组成,

public class MainViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
 
        private List<List<object>> _DataSource;
        public List<List<object>> DataSource
        {
            get { return _DataSource; }
            set
            {
                if (_DataSource != value)
                {
                    _DataSource = value;
                    this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DataSource)));
                }
            }
        }
        public MainViewModel()
        {
            //这里先创建临时变量,待数据填充完毕后赋值给DataSource,原因待会会说~
            var dataSource = new List<List<object>>();
            for (int i = 0; i < 5; i++)
            {
                var row = new List<object>();
                row.Add(DateTime.Now.AddDays(i));
                row.Add(new Factor { Name = "CO2", Min = "12.34" + i, Avg = "13.45" + i, Max = "14.56" + i, Flag = "N" });
                row.Add(new Factor { Name = "NO2", Min = "23.14" + i, Avg = "25.25" + i, Max = "27.36" + i, Flag = "N" });
                row.Add(new Factor { Name = "SO2", Min = "34.45" + i, Avg = "35.56" + i, Max = "36.67" + i, Flag = "N" });
                dataSource.Add(row);
            }
            DataSource = dataSource;
        }
    }

(2).主界面

添加一个DataGrid,并绑定数据源到DataSource

<DataGrid x:Name="dataGrid" 
          ItemsSource="{Binding DataSource}"
          AutoGenerateColumns="False"
          ColumnHeaderHeight="30" 
          RowHeight="30"/>

(3).后台

后台ViewModel绑定

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainViewModel();
        }
    }

此时通过监听DataGrid的ItemsSource,来创造动态绑定时机(也可以继承DataGrid,并重写OnItemsSourceChanged或OnItemsChanged方法来实现)

以下为监听ItemsSource并实现动态绑定的代码:

var dpDescriptor = DependencyPropertyDescriptor.FromProperty(DataGrid.ItemsSourceProperty, typeof(DataGrid));
dpDescriptor.AddValueChanged(dataGrid, (s, e) =>
{
    dataGrid.Columns.Clear();
    if (dataGrid.ItemsSource is List<List<object>> dataSource && dataSource?.Count > 0)
    {
        var firstRow = dataSource.First();
        for (int i = 0; i < firstRow.Count; i++)
        {
            if (firstRow[i] is Factor factor)
            {
                //待会实现
            }
            else
            {
                this.dataGrid.Columns.Add(new DataGridTextColumn() { Header = "时间", Binding = new Binding("[" + i.ToString() + "]") { StringFormat = "yyyy/MM/dd HH:mm:ss"} });
            }
        }
    }
});

二、自定义表头结构

方才定义了Factor数据结构,是想在同一列中显示多个字段,并且表头一一对齐。那么接下来要做两件事情:重写DataGridColumnHeader样式,实现一个针对Factor列的DataGridTemplateColumn.

1、在重写DataGridColumnHeader样式的时候,我希望通过数据绑定来实现子列标题排版。于是先定义一个针对Header的数据结构:

public class FactorColumnHeaderModel
{
    public string Header { get; set; }
    public List<string> SubHeaders { get; set; }
 
    /// <summary>
    /// 由于标记列可能不需要显示,所以单独用布尔类型绑定
    /// </summary>
    public bool HasFlag { get; set; } = true;
}

接着是重写的DataGridColumnHeader样式

<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<Style x:Key="FactorColumnHeaderStyle" TargetType="DataGridColumnHeader">
    <Setter Property="HorizontalAlignment" Value="Stretch"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="DataGridColumnHeader">
                <Grid HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
			Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="{Binding Header}" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.ColumnSpan="2" />
                    <ItemsControl Grid.Row="1" ItemsSource="{Binding SubHeaders}" HorizontalContentAlignment="Center">
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <UniformGrid Columns="{Binding SubHeaders.Count}"/>
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding .}" HorizontalAlignment="Center"/>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                    <TextBlock Text="标记" Grid.Row="1" Grid.Column="1" Width="50" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="{Binding HasFlag,Converter={StaticResource BooleanToVisibilityConverter}}"/>
                    <Thumb x:Name="PART_RightHeaderGripper" Grid.RowSpan="2" Grid.ColumnSpan="2" Cursor="SizeWE" HorizontalAlignment="Right" Width="2" Background="Transparent">
                        <Thumb.Template>
                            <ControlTemplate TargetType="Thumb">
                                <Rectangle Fill="{TemplateBinding Background}" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"/>
                            </ControlTemplate>
                        </Thumb.Template>
                    </Thumb>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

2、实现针对Factor列的DataGridTemplateColumn,由于需要动态绑定数据源,因此必须通过代码生成DataGridTemplateColumn的CellTemplate.

这里有两种做法:一是通过FrameworkElementFactory一个个创建元素,这种做法代码过长,已被我Pass。故采用第二种做法,创建DataTemlate的Xaml代码段,利用XamlReader生成DataTemplate实例。

代码段生成函数如下:

/// <summary>
/// 生成DataTemlate的代码片段
/// </summary>
/// <param name="cellBindingList">每个元素对应的数据绑定字符串集合</param>
/// <param name="hasFlag">是否包含独立的标记列(对应表头)</param>
/// <returns></returns>
public static string GetCellTemplateXaml(List<string> cellBindingList, bool hasFlag)
{
    var templateStr = "<DataTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">";
    templateStr += "<Grid><Grid.ColumnDefinitions><ColumnDefinition Width=\"*\" /><ColumnDefinition Width=\"Auto\" /></Grid.ColumnDefinitions>";
    templateStr += $"<UniformGrid Columns=\"{(hasFlag ? cellBindingList.Count - 1 : cellBindingList.Count)}\">";
    cellBindingList.ForEach(a =>
    {
        if (!hasFlag || a != cellBindingList.Last())
        {
            templateStr += "<TextBlock Margin=\"10 0 10 0\" Text=\"{Binding " + a + "}\" VerticalAlignment=\"Center\" HorizontalAlignment=\"Center\" />";
        }
    });
    templateStr += "</UniformGrid>";
    if (hasFlag)
    {
        templateStr += "<TextBlock Grid.Column=\"1\" Text=\"{Binding " + cellBindingList.Last() + "}\" Width=\"50\" TextAlignment=\"Center\" VerticalAlignment=\"Center\" HorizontalAlignment=\"Center\" />";
    }
    templateStr += "</Grid></DataTemplate>";
    return templateStr;
}

最终动态生成列部分的代码如下:

var dpDescriptor = DependencyPropertyDescriptor.FromProperty(DataGrid.ItemsSourceProperty, typeof(DataGrid));
dpDescriptor.AddValueChanged(dataGrid, (s, e) =>
{
    dataGrid.Columns.Clear();
 
    if (dataGrid.ItemsSource is List<List<object>> dataSource && dataSource?.Count > 0)
    {
        var columnHeaderStyle = this.FindResource("FactorColumnHeaderStyle") as Style;
        var firstRow = dataSource.First();
        for (int i = 0; i < firstRow.Count; i++)
        {
            if (firstRow[i] is Factor factor)
            {
                // 表头绑定的数据源
                var factorHeaderSource = new FactorColumnHeaderModel()
                {
                    HasFlag = true,
                    Header = factor.Name,
                    SubHeaders = new List<string>() { "最小值", "均值", "最大值" }
                };
 
                // 列中元素绑定的数据源
                var bindingList = new List<string>();
                bindingList.Add($"[{i}].Min");
                bindingList.Add($"[{i}].Avg");
                bindingList.Add($"[{i}].Max");
                bindingList.Add($"[{i}].Flag");
                var factorCellTemplateStr = GetCellTemplateXaml(bindingList, factorHeaderSource.HasFlag);
                var templateColumn = new DataGridTemplateColumn()
                {
                    Header = factorHeaderSource,
                    HeaderStyle = columnHeaderStyle,
                    CellTemplate = (DataTemplate)XamlReader.Parse(factorCellTemplateStr)
                };
                this.dataGrid.Columns.Add(templateColumn);
            }
            else
            {
                this.dataGrid.Columns.Add(new DataGridTextColumn() { Header = "时间", Binding = new Binding("[" + i.ToString() + "]") { StringFormat = "yyyy/MM/dd HH:mm:ss" } });
            }
        }
    }
});

3.实例二

1.使用DataGrid显示数据

第一步,在前端xaml文件上添加两个控件,一个是DataGrid控件,用于展示数据;另一个是Button控件,用于添加数据。前端PageUser.xaml文件的内容如下:

<Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="20" />
      <RowDefinition Height="5"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Button Grid.Row="0" Click="AddUser" Content="添加用户" />
    <DataGrid Grid.Row="2" AutoGenerateColumns="False"
              CanUserAddRows="False" CanUserDeleteRows="False" 
              CanUserReorderColumns="False" CanUserResizeColumns="False"
              CanUserResizeRows="False" ColumnHeaderHeight="30"
              FontSize="16" GridLinesVisibility="Horizontal" 
              HeadersVisibility="Column" IsReadOnly="True" Name="datagrid"
              ItemsSource="{Binding UserDB}" RowHeight="28">
      <DataGrid.Columns>
        <DataGridTextColumn Width="Auto" MinWidth="25" Binding="{Binding UserID}" Header=""/>
        <DataGridTextColumn Width="*" Binding="{Binding UserName}" Header="用户名"/>
        <DataGridTextColumn Width="*" Binding="{Binding UserAccount}" Header="帐号"/>
        <DataGridTextColumn Width="*" Binding="{Binding UserPasswd}" Header="密码"/>
        <DataGridTextColumn Width="*" Binding="{Binding UserPhone}" Header="电话"/>
        <DataGridTextColumn Width="*" Binding="{Binding UserEmail}" Header="邮箱"/>
        <DataGridTextColumn Width="0.6*" Binding="{Binding UserSex}" Header="性别"/>
      </DataGrid.Columns>
    </DataGrid>
  </Grid>

DataGrid控件中的ItemsSource="{Binding UserDB}"将DataGrid的数据源与UserDB属性绑定,也就表明数据源将从传入DataGrid控件数据对象的UserDB属性获取。DataGrid.Columns定义的是数据表的列(行头),Header的值是行头名,Binding绑定的是数据源对象中的子属性。数据源对象是一个数据对象数组,每一个数据对象需要包含UserID,UserName,UserAccount,UserPasswd,UserPhone,UserEmail,UserSex等属性。

第二步,后台cs文件实现数据展示及按钮触发的函数。前端PageUser.xaml.cs文件的内容如下:

 public partial class MainWindow : Window
    {
        private UserDBModel userdb;
        public MainWindow()
        {
            InitializeComponent();

            userdb = new UserDBModel();

            datagrid.DataContext = userdb;

        }
        private void AddUser(object sender, RoutedEventArgs e)
        {
            int num = userdb.UserDB.Count + 1;
            UserModel user = new UserModel((UInt32)num,
                                           "test" + num.ToString(),
                                           (UInt32)(1000000000 + num),
                                           "passwd" + num.ToString(),
                                           (UInt64)(10000000000 + num),
                                           "test@" + num.ToString() + ".com",
                                           num % 2 == 0);
            userdb.UserDB.Add(user);
        }

    }

    public class UserModel
    {
        public UInt32 UserID { get; set; }
        public string UserName { get; set; }
        public UInt32 UserAccount { get; set; }
        public string UserPasswd { get; set; }
        public UInt64 UserPhone { get; set; }
        public string UserEmail { get; set; }
        public bool UserSex { get; set; }
        public UserModel() { }
        public UserModel(UInt32 id,string name,UInt32 account,string passwd,UInt64 phone,string email,bool sex)
        {
            UserID = id;
            UserName = name;
            UserAccount = account;
            UserPasswd = passwd;
            UserPhone = phone;
            UserEmail = email;
            UserSex = sex;
        }
    }
    public class UserDBModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(PropertyChangedEventArgs e)  //当属性变化时候,能够跟新上去
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e);
        }
        private ObservableCollection<UserModel> userdb = new ObservableCollection<UserModel>();
        public ObservableCollection<UserModel> UserDB
        {
            get { return userdb; }
            set
            {
                userdb = value;
                OnPropertyChanged(new PropertyChangedEventArgs("UserDB"));
            }
        }
    }

在后台文件中,首先定义了两个类UserModel,UserDBModel。其中UserModel类就是前端所需的数据对象类型,而UserDBModel是前端所需的数据源对象类。写法与之前1篇提到的动态刷新LIstBox写法类似,要求UserDBModel继承自INotifyPropertyChanged,实现实时刷新。 在界面类中实例化一个UserDBModel类型的成员userdb,作为数据源对象。在按钮响应函数AddUser中,我们构造一个UserModel对象user,并将user添加进userdb的UserDB成员。

备注1: 如果不需要实现刷新,那么直接创建一个List类型的列表,作为datagrid的ItemSource也可。

2.DataGrid样式设计

1.设计交替变换的样式
前端xaml文件修改为:


  <Grid>
    ...
    <DataGrid Grid.Row="2" AlternationCount="2" ...>
      <DataGrid.RowStyle>
        <Style TargetType="{x:Type DataGridRow}">
          <Style.Triggers>
            <Trigger Property="AlternationIndex" Value="0">
              <Setter Property="Background" Value="#FFD9EEED"/>
            </Trigger>
            <Trigger Property="AlternationIndex" Value="1">
              <Setter Property="Background" Value="#FF84B3F9"/>
            </Trigger>
          </Style.Triggers>
        </Style>
      </DataGrid.RowStyle>
      ...
    </DataGrid>
  </Grid>

如上,在DataGrid中设置属性AlternationCount的值,AlternationCount表示交替长度,我们设置交替长度为2;再添加DataGrid.RowStyle样式,如上,当交替索引为0时,触发第一种Trigger样式,当交替索引为1时,触发第二种Trigger样式。
在这里插入图片描述
2.禁止用户按列对表中内容进行排序
我们可以通过设置DataGrid属性CanUserSortColumns的值来控制是否允许用户按列对表中内容进行排序,默认情况下CanUserSortColumns属性的值为True,我们将这个属性的值设置为False即可禁止用户按列对表中内容进行排序。

3.禁止列排序、禁止列宽调整
我们可以通过设置DataGrid属性CanUserReorderColumns的值来控制是否允许用户对列排序,默认情况下CanUserReorderColumns属性的值为True,我们将这个属性的值设置为False即可禁止用户对列排序。
我们可以通过设置DataGrid属性CanUserResizeColumns的值来控制是否允许用户对列宽调整,默认情况下CanUserResizeColumns属性的值为True,我们将这个属性的值设置为False即可禁止用户对列宽调整。

4.设置固定排序规则对列表排序
DataGrid的固定排序可以利用CollectionViewSource实现,通过CollectionViewSource将数据源先进行排序,再作为新的数据源传给DataGrid。

前端xaml文件如下所示:

<Window ...
      xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase">
  <Window.Resources>
    <CollectionViewSource x:Key="SortUserDB" Source="{Binding UserDB}">
      <CollectionViewSource.SortDescriptions>
        <scm:SortDescription PropertyName="UserID" Direction="Descending"/>
      </CollectionViewSource.SortDescriptions>
    </CollectionViewSource>
  </Window.Resources>
  <Grid>
    ...
    <DataGrid ...ItemsSource="{Binding Source={StaticResource SortUserDB}}">
      ...
    </DataGrid>
  </Grid>
</Window>
 public PageUser()
    {
      InitializeComponent();
      userdb = new UserDBModel();
      //datagrid.DataContext = userdb;
      this.DataContext = userdb;
    }

原本后台数据源的数据UserDB是直接传送给DataGrid的ItemsSource进行数据绑定,现在改为了先将数据传送给页面静态资源CollectionViewSource的Source,而DataGrid的ItemsSource绑定的是静态资源SortUserDB,即CollectionViewSource资源。在CollectionViewSource中,我们通过设置<CollectionViewSource.SortDescriptions>实现对列表的排序,<scm:SortDescription PropertyName=“UserID” Direction=“Descending”/>这一行是按照属性UserID的值对数据源进行排序,Direction="Descending"是逆序,默认情况为正序!(这里可以设置多种排序,如在按照UserID排序后,可以在此基础上按照其它属性再排序,直接在下方按照同样的手法再加一行而已。)

在这里插入图片描述
5.设置列宽为自适应宽度
只需要将列宽设置为Auto,就可以根据列表内容自适应宽度!

6.更换显示内容
在数据源中,我们通常采用bool类型来保存性别,但在前端,我们需要将性别显示为正常的男或女,要实现这个功能,有多种思路。第一种,从数据源头修改,即我们设置数据对象的UserSex属性值时,直接设置为"男"或"女";第二种,使用模板列;第三种,触发后台修改,每次数据源更新时,触发后台函数,修改前端数据表性别的显示方法;第四种,采用数据源转换器,在Binding时,使用Converter转换器直接将数据源转换后传给目标。

我们介绍下第四种方法:
第一步,在后台cs文件中创建转换器类

namespace wpfbase
{
  ...
  public class SexConverter: IValueConverter {
    public object Convert(
        object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture) {
      if((bool)value) return "男";
      else return "女";
    }
    public object ConvertBack(
        object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture) {
      if((string)value == "男") return true;
      else return false;
    }
  }
}

在后台我们创建了一个继承至IValueConverter的SexConverter类,在这个类中我们必须覆盖两个接口函数,Convert和ConvertBack。Convert函数实现了从数据源到呈现目标的数据转换,其中true对应的是"男",false对应的是"女";ConvertBack函数实现了从呈现目标到数据源的转换,"男"对应的是true,"女"对应的是false。

第二步,在前端文件中使用转换器

<Window ...
      xmlns:local="clr-namespace:wpfbase"
      ...>
  <Window.Resources>
    <local:SexConverter x:Key="UserSexConverter"/>
    ...
  </Window.Resources>
  <Grid>
    ...
    <DataGrid ...>
      ...
      <DataGrid.Columns>
        ...
        <DataGridTextColumn Width="Auto" Binding="{Binding UserSex, Converter={StaticResource UserSexConverter}}" Header="性别"/>
      </DataGrid.Columns>
    </DataGrid>
  </Grid>
</Window>

首先引入转换器类的命名空间,由于我们实在项目的命名空间下wpfbase定义的类,不需要再引入命名空间。在页面的资源中添加转换器资源,<local:SexConverter x:Key=“UserSexConverter”/>,SexConverter是后台文件中定义的类名,UserSexConverter是现在定义的转换器资源名称,在后面使用,这里注意,添加这行代码时编译器可能会提醒"命名空间中找不到此名称",不用在意这个,继续往下做。在性别那一列的绑定数据中,Binding="{Binding UserSex, Converter={StaticResource UserSexConverter}}"使用转换器资源实现数据内容的转换。

7.设置单元格区间范围,超出范围修改样式

3.DataGrid连接MySQL数据库显示数据表

使用datagrid控件将数据表展示在前端界面上。

前端xaml文件内容如下:

<Window ... Title="PageClient">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="20" />
      <RowDefinition Height="5"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <DataGrid Grid.Row="2" AlternationCount="2" AutoGenerateColumns="False"
              CanUserAddRows="False" CanUserDeleteRows="False" 
              CanUserReorderColumns="False" CanUserResizeColumns="False"
              CanUserSortColumns="False"
              CanUserResizeRows="False" ColumnHeaderHeight="30"
              FontSize="16" GridLinesVisibility="Horizontal" 
              HeadersVisibility="Column" IsReadOnly="True" Name="datagrid"
              RowHeight="28">
      <DataGrid.RowStyle>...</DataGrid.RowStyle>
      <DataGrid.Columns>
        <DataGridTextColumn Width="Auto" MinWidth="25" Binding="{Binding UserID}" Header=""/>
        <DataGridTextColumn Width="Auto" Binding="{Binding UserName}" Header="用户名"/>
        <DataGridTextColumn Width="Auto" Binding="{Binding UserAccount}" Header="帐号"/>
        <DataGridTextColumn Width="Auto" Binding="{Binding UserPasswd}" Header="密码"/>
        <DataGridTextColumn Width="Auto" Binding="{Binding UserPhone}" Header="电话"/>
        <DataGridTextColumn Width="Auto" Binding="{Binding UserEmail}" Header="邮箱"/>
        <DataGridTextColumn Width="Auto" Binding="{Binding UserSex}" Header="性别"/>
      </DataGrid.Columns>
    </DataGrid>
  </Grid>
</Window>

前端文件的内容基本不变,DataGrid的源ItemSource暂不绑定,采用第一大点中的备注1的方式确定数据源。
后端文件内容如下:

using System.Data;
using System.Windows.Controls;
using MySql.Data.MySqlClient;

namespace wpfbase
{
  public partial class PageClient : Page
  {
    MySqlConnection conn;
    MySqlDataAdapter msda;
    MySqlCommand cmd;
    public PageClient()
    {
      InitializeComponent();
      string connStr = "server=127.0.0.1;user=root;password=rootmysql; database=wpfbasedb;";
      conn = new MySqlConnection(connStr);
      conn.Open();
      string sql = "select * from usertable";
      cmd = new MySqlCommand(sql, conn);
      cmd.CommandType = CommandType.Text;
      DataTable dt = new DataTable();
      msda = new MySqlDataAdapter(cmd);
      msda.Fill(dt);
      datagrid.ItemsSource = dt.DefaultView; // 数据绑定
      conn.Close();
    }
  }
}

后台将从数据库查到的数据以DataTable.DefaultView的方式传给datagrid。
样式和转换器的写法与之前相同!

4.DataGrid实时刷新MySQL数据库显示数据(待补充)

如果要实时刷新数据库的状态,有两种方式,一种是定时轮询的方式访问数据库,另一种则是监控数据库的数据表,当数据表发生变化时,向客户端发送通知。SQL中有SqlDependency类可以实现通知功能,当数据表变化时,会触发OnChange。

5.DataGrid属性说明:此处有链接

组件常用方法:
BeginEdit:使DataGrid进入编辑状态。
CancelEdit:取消DataGrid的编辑状态。
CollapseRowGroup:闭合DataGrid的行分组。
CommitEdit:确认DataGrid的编辑完成。
ExpandRowGroup:展开DataGrid的行分组。
GetGroupFromItem:从具体Item中得到分组。
ScrollIntoView:滚动DataGrid视图。

组件常用属性:
AlternatingRowBackground:获取或设置一个笔刷用来描绘DataGrid奇数行的背景。
AreRowDetailsFrozen:获取或设置一个值用来判断是否冻结每行内容的详细信息。
AreRowGroupHeadersFrozen:获取或设置一个值用来判断是否冻结分组行的头部。
AutoGenerateColumns:获取或设置一个值用来判断是否允许自动生成表列。
CanUserReorderColumns:获取或设置一个值用来判断是否允许用户重新排列表列的位置。
CanUserSortColumns:获取或设置一个值用来判断是否允许用户按列对表中内容进行排序。
CellStyle:获取或设置单元格的样式。
ColumnHeaderHeight:获取或设置列头的高度。
ColumnHeaderStyle:获取或设置列头的样式。
Columns:获取组件中包含所有列的集合。
ColumnWidth:获取或设置列宽。
CurrentColumn:获取或设置包含当前单元格的列。
CurrentItem:获取包含当前单元格且与行绑定的数据项。
DragIndicatorStyle:获取或设置当拖曳列头时的样式。
DropLocationIndicatorStyle:获取或设置呈现列头时的样式。
FrozenColumnCount:获取或设置冻结列的个数。
GridLinesVisibility:获取或设置网格线的显示形式。
HeadersVisibility:获取或设置行头及列头的显示形式。
HorizontalGridLinesBrush:获取或设置水平网格线的笔刷。
HorizontalScrollBarVisibility:获取或设置水平滚动条的显示样式。
IsReadOnly:获取或设置DataGrid是否为只读。
MaxColumnWidth:获取或设置DataGrid的最大列宽。
MinColumnWidth:获取或设置DataGrid的最小列宽。
RowBackground:获取或设置用于填充行背景的笔刷。
RowDetailsTemplate:获取或设置被用于显示行详细部分的内容的模板。
RowDetailsVisibilityMode:获取或设置一个值用以判定行详细部分是否显示。
RowGroupHeaderStyles:获取呈现行分组头部的样式。
RowHeaderStyle:获取或设置呈现行头的样式。
RowHeaderWidth:获取或设置行头的宽度。
RowHeight:获取或设置每行的高度。
RowStyle:获取或设置呈现行时的样式。
SelectedIndex:获取或设置当前选中部分的索引值。
SelectedItem:获取或设置与当前被选中行绑定的数据项。
SelectedItems:获取与当前被选中的各行绑定的数据项们的列表(List)。
SelectionMode:获取或设置DataGrid的选取模式。
VerticalGridLinesBrush:获取或设置垂直网格线的笔刷。
VerticalScrollBarVisibility:获取或设置垂直滚动条的显示样式。

组件常用事件:
BeginningEdit:发生于一个单元格或行进入编辑模式之前。
CellEditEnded:发生于一个单元格编辑已被确认或取消。
CellEditEnding:发生于一个单元格正在结束编辑时。
CurrentCellChanged:发生于一个单元格成为当前单元格时。
PreparingCellForEdit:发生于在DataGridTemplateColumn下的单元格进入编辑模式时。
SelectionChanged:发生于当SelectedItem或SelectedItems属性值改变时。

链接:
1.DataGrid
2.DataGrid
3.DataGrid属性说明

六.DataContext 使用

数据绑定中使用DataContext 数据上下文,DataContext 属性是绑定的默认源,除非你特别声明另一个源。DataContext 属性没有默认源(从一开始就是 null),但是由于 DataContext 是通过控件层次结构向下继承的,因此可以为 Window 本身设置一个 DataContext,然后在所有子控件中使用它。

<Grid>
	<StackPanel Margin="15">
		<WrapPanel>
		<TextBlock Text="Window title: " />
		<TextBox Text="{Binding Title, UpdateSourceTrigger=PropertyChanged}" Width="150" />
		</WrapPanel>
		<WrapPanel Margin="0,10,0,0">
		<TextBlock Text="Window dimensions: " />
		<TextBox Text="{Binding Width}" Width="50" />
		<TextBlock Text=" x " />
		<TextBox Text="{Binding Height}" Width="50" />
		</WrapPanel>
	</StackPanel>
</Grid>
public partial class MainWindow : Window
{
	public MainWindow()
	{
		InitializeComponent();
		this.DataContext = this;
	}
}

这个例子的代码隐藏只添加了一行有趣的代码:在标准的 InitalizeComponent() 调用之后,我们将“this”引用分配给 DataContext,它只是告诉窗口我们希望自己成为数据上下文。在 XAML 中,我们使用这个事实来绑定到几个 Window 属性,包括 Title、Width 和 Height。由于窗口有一个 DataContext,它被传递给子控件,我们不必在每个绑定上定义一个源 - 我们只需使用这些值,就好像它们是全局可用的一样。

尝试运行示例并调整窗口大小 - 您将看到尺寸更改立即反映在文本框中。您也可以尝试在第一个文本框中编写不同的标题,但您可能会惊讶地发现此更改并未立即反映。相反,您必须在应用更改之前将焦点移动到另一个控件。
使用 DataContext 属性就像通过控件的层次结构向下设置所有绑定的基础。这为您节省了为每个绑定手动定义源的麻烦,并且一旦您真正开始使用数据绑定,您一定会喜欢节省的时间和输入。

但是,这并不意味着您必须对 Window 中的所有控件使用相同的 DataContext。由于每个控件都有自己的 DataContext 属性,因此您可以轻松打破继承链并使用新值覆盖 DataContext。这允许您做一些事情,例如在窗口上拥有一个全局 DataContext,然后在一个面板上拥有一个单独的表单或类似的东西上一个更本地和特定的DataContext。

窗口绑定字段属性。

  1. 使用窗口类的DataContext
public partial class MainWindow : Window
{
	public string MyProperty { get; set; } = "Test ";
	public int MyInt { get; set; } = 123;
	
	public MainWindow()
	{
		InitializeComponent();
		this.DataContext = this;
	}
}
<Grid>
	<StackPanel>
		<TextBox FontSize="48" Text="{Binding MyProperty}"/>
		<TextBox FontSize="48" Text="{Binding MyInt}"/>
	</StackPanel>
</Grid>

创建窗口2个属性字段,一个字符串属性,一个整型属性。this.DataContext = this; 窗口界面就使用这个2个属性值的绑定。

  1. 创建一个类,将类的实例赋值到DataContext
public class Student
{
	public string Name { get; set; } = "Josh";
	public int Id { get; set; } = 1002;
}

public MainWindow()
{
	InitializeComponent();
	Student std = new Student();
	this.DataContext = std;
}
<Grid>
	<StackPanel>
		<TextBox FontSize="48" Text="{Binding Id}"/>
		<TextBox FontSize="48" Text="{Binding Name}"/>
	</StackPanel>
</Grid>
  1. 创建界面子元素的DataContext 属性绑定
public class Employee
{
	public int Id { get; set; } = 1001;
	public string Name { get; set; } = "Mike";
}
<Grid>
	<StackPanel >
		<StackPanel.DataContext>
			<local:Employee x:Name="Emp"></local:Employee>
		</StackPanel.DataContext>
		<TextBox FontSize="48" Text="{Binding Id}"/>
		<TextBox FontSize="48" Text="{Binding Name}"/>
		<StackPanel>
			<StackPanel.DataContext>
				<local:Student x:Name="Stu"></local:Student>
			</StackPanel.DataContext>
			<TextBox FontSize="48" Text="{Binding Id}"/>
			<TextBox FontSize="48" Text="{Binding Name}"/>
		</StackPanel>
	</StackPanel>
</Grid>

当然类的属性值时可以动态变化的,所以绑定的值也动态可变的。
链接:
1.DataContext

七.WrapPanelWrapPanel

用于一个接一个的排列子控件,以水平或者垂直方向,当空间不足时就会自动切换到下一行。适合于需要水平或者垂直排列控件且能自动换行的情况。水平方向排列时,每一行所有子控件的高度都被统一成固定的值,这个值由最高的那个决定;每一列垂直方向排列时,所有子控件的宽度都被统一成固定的值,这个值由最宽的那个决定。
我们先来看默认情况下的WrapPanel:

<Window x:Class="WpfTutorialSamples.Panels.WrapPanel"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WrapPanel" Height="300" Width="300">
        <WrapPanel>
                <Button>Test button 1</Button>
                <Button>Test button 2</Button>
                <Button>Test button 3</Button>
                <Button Height="40">Test button 4</Button>
                <Button>Test button 5</Button>
                <Button>Test button 6</Button>
        </WrapPanel>
</Window>

在这里插入图片描述
注意我为第二行中的一个按钮指定了一个高度,这就使得第二行所有按钮都被设置成了这个高度。另外,这个面板还做了一件事件:第一行放不下的时候,自动了切换到第二行。如果你改变窗口大小,譬如缩小窗口,面板立马自动调整以适应。
这些规则在垂直方向排列时也是一样的。
特别要注意,水平向的WrapPanel自动匹配同一行的高度,而不会匹配宽度;垂直向的WrapPanel自动匹配同一列的宽度,而不会匹配高度。看下面的例子,垂直向的WrapPanel在第四个按钮同时设置了宽和高:

Test button 4
在这里插入图片描述
可以看出第五个按钮只使用了第四个按钮的宽,并没有使用高。于是第6个按钮被挤到了第三列。
链接:
1.WrapPanel

⬇转到底部

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值