年度巨獻-WPF項目開發過程中WPF小知識點匯總(原創+摘抄)


WPF中Style的使用

  Styel在英文中解釋為”樣式“,在Web開發中,css為層疊樣式表,自從.net3.0推出WPF以來,WPF也有樣式一說,通過設置樣式,使其WPF控件外觀更加美化同時減少了大量的復雜屬性的設置。

   在WPF中,設置外觀樣式我們有很多種方式,比如通過設置控件的屬性來控制控件的外觀樣式;或者通過在每一個控件中分別設置Style;或者通過在整個Window.Resource中設置Style,又或者在App.xaml的Application.Resource設置Style。

   在此我們就不討論第一種方式設置控件的外觀了,因為這不涉及到Style的使用。那么后三種設置樣式來控制控件的外觀有什么區別呢?那么我們來分別討論吧!

   第一,通過在每一個控件中分別設置Style來控制控件的外觀,示例代碼如下:

 

            <Button Content="Button" Height="23" Name="button3" Width="75">
                <Button.Style>
                    <Style TargetType="Button">
                        <Setter Property="Background" Value="Black" />
                    </Style>
                </Button.Style>
            </Button>

以上樣式的設置只正對當前的Button有效,與其他同種類型的控件無關。

  第二,通過在Window.Resource中設置Style來控制控件的外觀,示例代碼如下:

 

    <Window.Resources>
        <Style TargetType="Button">
            <Setter Property="Background" Value="LightBlue" />
        </Style>
    </Window.Resources>

以上樣式的設置,針對整個Window的所有Button有效(只要沒有單獨的對Button設置),這種方法呢,相對於第一種來說減少了代碼量。同時修改起來出錯的可能性較小!

第三,通過在App.xaml中的Application.Resource中設置Style來控制控件的外觀,示例代碼如下:

<Application.Resource>

<Style TargetType="Button">
            <Setter Property="FontFamily" Value="MS Reference Sans Serif" />
            <Setter Property="Background">
                <Setter.Value>
                    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                        <GradientStop Color="White" Offset="0"/>
                        <GradientStop Color="SkyBlue" Offset="0.2"/>
                        <GradientStop Color="SkyBlue" Offset="0.8"/>
                        <GradientStop Color="White" Offset="1"/>
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>
        </Style>

</Application.Resource>
View Code

 

以上樣式的設置,針對整個Application的所有Button有效(只要在Window.Resource或者獨立的Button中沒有分別設置),這種方法來設置控件的外觀樣式呢,相對於前兩種來說,代碼量有大幅的增加,同時呢,在一個應用程序中,往往同種類型的控件的很多屬性都是相同的,我們在Applicaiton.Resource進行全局設置,使其維護更加方便!

  好了,以上是我對WPF中Style的理解,希望在我總結的同時,能夠給同行們提供幫助,如果發現錯誤,請積極指正,謝謝!

WPF ListBox 橫向排列(摘抄)

 

如果只是單純的讓ListBox可以橫向配列,這樣很簡單,只需要更改ListBox的ItemsPanel模板就可以,例如:

<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation=”Horizontal” IsItemsHost=”True”/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
  但是這樣的修改,ListBox只能橫向排列,不會根據寬度自動換行,如果想要橫向排列的ListBox支持根據寬度自動換行的話,需要這樣寫:

<ListBox.Template>
<ControlTemplate TargetType=”{x:Type ListBox}”>
<ScrollViewer HorizontalScrollBarVisibility=”Disabled” VerticalScrollBarVisibility=”Auto”>
<WrapPanel Orientation=”Horizontal” IsItemsHost=”True” ScrollViewer.CanContentScroll=”True”/>
</ScrollViewer>
</ControlTemplate>
</ListBox.Template>
 
<Style TargetType="{x:Type ListBox}">
  <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
  <Setter Property="ItemsPanel">
    <Setter.Value>
      <ItemsPanelTemplate>
        <WrapPanel Orientation="Horizontal" Margin="2" Background="LightGray"/>
      </ItemsPanelTemplate>
    </Setter.Value>
  </Setter>
  <Setter Property="ItemContainerStyle">
    <Setter.Value>
      <Style TargetType="ListBoxItem">
        <Style.Triggers>
          <Trigger Property="IsSelected" Value="True">
            <Trigger.Setters>
              <Setter Property="BorderThickness" Value="1"/>
              <Setter Property="BorderBrush" Value="Red"/>
            </Trigger.Setters>
          </Trigger>
        </Style.Triggers>
      </Style>
    </Setter.Value>
  </Setter>
</Style>
View Code

 

截取字符串總結

string str="123abc456";
int i=3;
1 取字符串的前i個字符
   str=str.Substring(0,i); // or str=str.Remove(i,str.Length-i);
2 去掉字符串的前i個字符:
   str=str.Remove(0,i); // or str=str.Substring(i);
3 從右邊開始取i個字符:
  str=str.Substring(str.Length-i); // or str=str.Remove(0,str.Length-i);
4 從右邊開始去掉i個字符:
   str=str.Substring(0,str.Length-i); // or str=str.Remove(str.Length-i,i);
5 判斷字符串中是否有"abc" 有則去掉之
   using System.Text.RegularExpressions;
   string str = "123abc456";
   string a="abc";
   Regex r = new Regex(a);
   Match m = r.Match(str);
   if (m.Success)
   {
    //下面兩個取一種即可。
      str=str.Replace(a,"");
      Response.Write(str);
      string str1,str2;
      str1=str.Substring(0,m.Index);
      str2=str.Substring(m.Index+a.Length,str.Length-a.Length-m.Index);
      Response.Write(str1+str2);
   }
6 如果字符串中有"abc"則替換成"ABC"
   str=str.Replace("abc","ABC");
 
************************************************
 
string str="adcdef"; int indexStart = str.IndexOf("d");
 
int endIndex =str.IndexOf("e");
 
string toStr = str.SubString(indexStart,endIndex-indexStart);
 
c#截取字符串最后一個字符的問題!
 
str1.Substring(str1.LastIndexOf(",")+1);
 
C# 截取字符串最后一個字符
 
k = k.Substring(k.Length-1, 1);
View Code

 

wpf移動圖片變大

 
<Grid>
        <Canvas x:Name="LayoutRoot">
            <Image Cursor="Hand" MouseLeftButtonDown="imgLogo1_MouseLeftButtonDown" MouseEnter="imgLogo1_MouseEnter"
                   MouseLeave="imgLogo1_MouseLeave" Canvas.ZIndex="1" x:Name="imgLogo1" Canvas.Left="100"
                   Canvas.Top="60" Height="100" Source="Image/Picture.jpg">
                <Image.RenderTransform>
                    <ScaleTransform x:Name="LogoScale" CenterX="90" CenterY="96">
 
                    </ScaleTransform>
                </Image.RenderTransform>
            </Image>
        </Canvas>
    </Grid>
  
 
public partial class Window8 : Window
   {
       public Window8()
       {
           InitializeComponent();
           timer = new System.Windows.Threading.DispatcherTimer();
           timer.Interval = TimeSpan.FromMilliseconds(50);
           timer.Tick += new EventHandler(timer_Tick);
       }
 
       private System.Windows.Threading.DispatcherTimer timer;
       private ScaleDirection scaleDirection ;
       
 
       void timer_Tick(object sender, EventArgs e)
       {
           AdjustScale(scaleDirection, LogoScale);
       }
 
       void AdjustScale(ScaleDirection scaleDirection, ScaleTransform scale)
       {
           if (scaleDirection == ScaleDirection.Down)
           {
               if (scale.ScaleX < 1.3)
               {
                   scale.ScaleX += 0.05; scale.ScaleY += 0.05;
               }
               else
                   timer.Stop();
           }
           else
           {
               if (scale.ScaleX > 1.0)
               {
                   scale.ScaleX -= 0.05;
                   scale.ScaleY -= 0.05;
               }
               else
                   timer.Stop();
           }
       }
 
       enum ScaleDirection
       {
           Up,
           Down
       }
 
       private void imgLogo1_MouseEnter(object sender, MouseEventArgs e)
       {
           scaleDirection = ScaleDirection.Down;
           timer.Start();
       }
 
       private void imgLogo1_MouseLeave(object sender, MouseEventArgs e)
       {
           scaleDirection = ScaleDirection.Up;
           timer.Start();
       }
 
       private void imgLogo1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
       {
           MessageBox.Show("test");
       }
   }
View Code

 

WPF的windows Triggers

<Window.Triggers>
        <EventTrigger SourceName="CrazyButton"
                      RoutedEvent="Window.Loaded">
            <!--<EventTrigger.Actions>-->
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetName="image2"
                                         Storyboard.TargetProperty="Opacity"
                                         To="0"
                                         Duration="0:0:5"
                                         AutoReverse="False"
                                         RepeatBehavior="Forever">
                            </DoubleAnimation>
                    </Storyboard>
                </BeginStoryboard>
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetName="image2"
                                         Storyboard.TargetProperty="(Canvas.Left)"
                                         To="500"
                                         Duration="0:0:5"
                                         AutoReverse="False"
                                         RepeatBehavior="Forever"/>
                    </Storyboard>
                </BeginStoryboard>
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetName="image1"
                                         Storyboard.TargetProperty="(Canvas.Left)"
                                         To="500"
                                         Duration="0:0:5"
                                         AutoReverse="False"
                                         RepeatBehavior="Forever"/>
                    </Storyboard>
                </BeginStoryboard>
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetName="image1"
                                         Storyboard.TargetProperty="Opacity"
                                         To="1"
                                         Duration="0:0:5"
                                         AutoReverse="False"
                                         RepeatBehavior="Forever">
                        </DoubleAnimation>
                    </Storyboard>
                </BeginStoryboard>
            <!--</EventTrigger.Actions>-->
        </EventTrigger>
    </Window.Triggers>
View Code

 

WPF的Converter

1.建類,必須繼承IValueConverter接口,在命名空間System.Windows.Data下
    class BoolToContentConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            bool temp = bool.Parse(value.ToString());
            if (temp)
                return "暫 停";
            else
                return "開 始";
        }
 
 
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
 
 
    }
 
2.在資源文件xaml里加入 轉換器,converters為命名空間定義 比如xmlns:converters="clr-namespace:Converters"
 <converters:BoolToContentConverter x:Key="BoolToContentConverter"/>
 
3.使用轉換器
<Button Content="{Binding Path=isDownloading, Converter={StaticResource BoolToContentConverter}}" > </Button>
 
4,帶參數的Converter
<TextBox  Text="{Binding XXXX,Converter={StaticResource UsrConverter},

  ConverterParameter={StaticResource userList }}" /> 
View Code

 

Converter 里對參數的使用(摘抄) 

public class UserNameConverter : IValueConverter

{

    public object IValueConverter.Convert(object value, Type targetType,object parameter, CultureInfo culture)

    {

      List<User> usrs = parameter as List<User>;

      ...

    }

}
View Code

 

如何學好wpf(摘抄)


用了三年多的WPF,開發了很多個WPF的項目,就我自己的經驗,談一談如何學好WPF,當然,拋磚引玉,如果您有什么建議也希望不吝賜教。

  WPF,全名是Windows Presentation Foundation,是微軟在.net3.0 WinFX中提出的。WPF是對Direct3D的托管封裝,它的圖形表現依賴於顯卡。當然,作為一種更高層次的封裝,對於硬件本身不支持的一些圖形特效的硬實現,WPF提供了利用CPU進行計算的軟實現,用以簡化開發人員的工作。

  簡單的介紹了一下WPF,這方面的資料也有很多。作於微軟力推的技術,整個推行也符合微軟一貫的風格。簡單,易用,強大,外加幾個創新概念的噱頭。

  噱頭一:聲明式編程。從理論上講,這個不算什么創新。Web界面聲明式開發早已如火如荼了,這種界面層的聲明式開發也是大勢所趨。為了適應聲明式編程,微軟推出了XAML,一種擴展的XML語言,並且在.NET 3.0中加入了XAML的編譯器和運行時解析器。XAML加上IDE強大的智能感知,確實大大方便了界面的描述,這點是值得肯定的。

  噱頭二:緊接着,微軟借XAML描繪了一副更為美好的圖片,界面設計者和代碼開發者可以並行的工作,兩者通過XAML進行交互,實現設計和實現的分離。不得不說,這個想法非常打動人心。以往設計人員大多是通過photoshop編輯出來的圖片來和開發人員進行交互的,需要開發人員根據圖片的樣式來進行轉換,以生成實際的效果。既然有了這層轉換,所以最終出來的效果和設計時總會有偏差,所以很多時候開發人員不得不忍受設計人員的抱怨。WPF的出現給開發人員看到了一線曙光,我只負責邏輯代碼,UI你自己去搞,一結合就可以了,不錯。可實際開發中,這里又出現了問題,UI的XAML部分能完全丟給設計人員么?

  這個話題展開可能有點長,微軟提供了Expression Studio套裝來支持用工具生成XAML。那么這套工具是否能夠滿足設計人員的需要呢?經過和很多設計人員和開發人員的配合,最常聽到的話類似於這樣。“這個沒有Photoshop好用,會限制我的靈感”, “他們生成的XAML太糟糕了...”。確實,在同一項目中,設計人員使用Blend進行設計,開發人員用VS來開發代碼邏輯,這個想法稍有理想化: 
  · 有些UI效果是很難或者不可以用XAML來描述的,需要手動編寫效果。 
  · 大多數設計人員很難接受面向對象思維,包括對資源(Resource)的復用也不理想 
  · 用Blend生成的XAML代碼並不高效,一種很簡單的布局也可能被翻譯成很冗長的XAML。

  在經歷過這樣不愉快的配合后,很多公司引入了一個integrator的概念。專門抽出一個比較有經驗的開發人員,負責把設計人員提供的XAML代碼整理成比較符合要求的XAML,並且在設計人員無法實現XAML的情況下,根據設計人員的需要來編寫XAML或者手動編寫代碼。關於這方面,我的經驗是,設計人員放棄Blend,使用Expression Design。Design工具還是比較符合設計人員的思維,當然,需要特別注意一些像素對齊方面的小問題。開發人員再通過設計人員提供的design文件轉化到項目中。這里一般是用Blend打開工程,Expression系列復制粘貼是格式化到剪切板的,所以可以在design文件中選中某一個圖形,點復制,再切到blend對應的父節點下點粘貼,適當修改一下轉化過來的效果。

  作為一個矢量化圖形工具,Expression Studio確實給我們提供了很多幫助,也可以達到設計人員同開發人員進行合作,不過,不像微軟描述的那樣自然。總的來說,還好,但是不夠好。

  這里,要步入本篇文章的重點了,也是我很多時候聽起來很無奈的事情。微軟在宣傳WPF時過於宣傳XAML和工具的簡易性了,造成很多剛接觸WPF的朋友們會產生這樣一副想法。WPF=XAML? 哦,類似HTML的玩意...

  這個是不對的,或者是不能這么說的。作為一款新的圖形引擎,以Foundation作為后綴,代表了微軟的野心。借助於托管平台的支持,微軟寄希望WPF打破長久以來桌面開發和Web開發的壁壘。當然,由於需要.net3.0+版本的支持,XBAP已經逐漸被Silverlight所取替。在整個WPF的設計里,XAML(Markup)確實是他的亮點,也吸取了Web開發的精華。XAML對於幫助UI和實現的分離,有如如虎添翼。但XAML並不是WPF獨有的,包括WF等其他技術也在使用它,如果你願意,所有的UI你也可以完成用后台代碼來實現。正是為了說明這個概念,Petzold在Application = codes + markup 一書中一分為二,前半本書完全使用Code來實現的,后面才講解了XAML以及在XAML中聲明UI等。但這本書叫好不叫座,你有一定開發經驗回頭來看發現條條是路,非常經典,但你抱着這本書入門的話估計你可能就會一頭霧水了。

  所以很多朋友來抱怨,WPF的學習太曲折了,上手很容易,可是深入一些就比較困難,經常碰到一些詭異困難的問題,最后只能推到不能做,不支持。復雜是由數量級別決定的,這里借LearnWPF的一些數據,來對比一下Asp.net, WinForm和WPF 類型以及類的數量:

ASP.NET 2.0

WinForms 2.0

WPF

 

1098 public types

1551 classes

777 public types

1500 classes

1577 public types

3592 classes

  當然,這個數字未必准確,也不能由此說明WPF相比Asp.net、WinForm,有多復雜。但是面對如此龐大的類庫,想要做到一覽眾山小也是很困難的。想要搞定一個大家伙,我們就要把握它的脈絡,所謂庖丁解牛,也需要知道在哪下刀。在正式談如何學好WPF之前,我想和朋友們談一下如何學好一門新技術。

  學習新技術有很多種途經,自學,培訓等等。相對於我們來說,聽說一門新技術,引起我們的興趣,查詢相關講解的書籍(資料),邊看書邊動手寫寫Sample這種方式應該算最常見的。那么怎么樣才算學好了,怎么樣才算是學會了呢?在這里,解釋下知識樹的概念:

  這不是什么創造性的概念,也不想就此談大。我感覺學習主要是兩方面的事情,一方面是向內,一方面是向外。這棵所謂樹的底層就是一些基礎,當然,只是個舉例,具體圖中是不是這樣的邏輯就不要見怪了。學習,就是一個不斷豐富自己知識樹的過程,我們一方面在努力的學習新東西,為它添枝加葉;另一方面,也會不停的思考,理清脈絡。這里談一下向內的概念,並不是沒有學會底層一些的東西,上面的東西就全是空中樓閣了。很少有一門技術是僅僅從一方面發展來的,就是說它肯定不是只有一個根的。比方說沒有學過IL,我並不認為.NET就無法學好,你可以從另外一個根,從相對高一些的抽象上來理解它。但是對底層,對這種關鍵的根,學一學它還是有助於我們理解的。這里我的感覺是,向內的探索是無止境的,向外的擴展是無限可能的。

  介紹了這個,接下來細談一下如何學好一門新技術,也就是如何添磚加瓦。學習一門技術,就像新new了一個對象,你對它有了個大致了解,但它是游離在你的知識樹之外的,你要做的很重要的一步就是把它連好。當然這層向內的連接不是一夕之功,可能會連錯,可能會少連。我對學好的理解是要從外到內,再從內到外,就讀書的例子談一下這個過程:

  市面關於技術的書很多,名字也五花八門的,簡單的整理一下,分為三類,就叫V1,V2,V3吧。 
· V1類,名字一般比較好認,類似30天學通XXX,一步一步XXX…沒錯,入門類書。這種書大致上都是以展示為主的,一個一個Sample,一步一步的帶你過一下整個技術。大多數我們學習也都是從這開始的,倒杯茶水,打開電子書,再打開VS,敲敲代碼,只要注意力集中些,基本不會跟不上。學完這一步,你應該對這門技術有了一定的了解,當然,你腦海中肯定不自覺的為這個向內連了很多線,當然不一定正確,不過這個新東東的創建已經有輪廓了,我們說,已經達到了從外的目的。 
· V2類,名字就比較亂了,其實意思差不多,只是用的詞語不一樣。這類有深入解析XXX,XXX本質論…這種書良莠不齊,有些明明是入門類書非要換個馬甲。這類書主要是詳細的講一下書中的各個Feature, 來龍去脈,幫你更好的認識這門技術。如果你是帶着問題去的,大多數也會幫你理清,書中也會順帶提一下這個技術的來源,幫你更好的把請脈絡。這種書是可以看出作者的功力的,是不是真正達到了深入淺出。這個過程結束,我們說,已經達到了從外到內的目的。 
· V3類,如果你認真,踏實的走過了前兩個階段,我覺得在簡歷上寫個精通也不為過。這里提到V3,其實名字上和V2也差不多。往內走的越深,越有種沖動想把這東西搞透,就像被強行注入了內力,雖然和體內脈絡已經和諧了,不過總該自己試試怎么流轉吧。這里談到的就是由內向外的過程,第一本給我留下深刻印象的書就是侯捷老師的深入淺出MFC,在這本書中,侯捷老師從零開始,一步一步的構建起了整個類MFC的框架結構。書讀兩遍,如醍醐灌頂,痛快淋漓。如果朋友們有這種有思想,講思想,有匠心的書也希望多多推薦,共同進步。

  回過頭,就這個說一下WPF。WPF現在的書也有不少,入門的書我首推MSDN。其實我覺得作為入門類的書,微軟的介紹就已經很好了,面面俱到,用詞准確,Sample附帶的也不錯。再往下走,比如Sams.Windows.Presentation.Foundation.Unleashed或者Apress_Pro_WPF_Windows_Presentation_Foundation_in_NET_3_0也都非常不錯。這里沒有看到太深入的文章,偶有深入的也都是一筆帶過,或者是直接用Reflector展示一下Code。

  接下來,談一下WPF的一些Feature。因為工作關系,經常要給同事們培訓講解WPF,越來越發現,學好學懂未必能講懂講透,慢慢才體會到,這是一個插入點的問題。大家的水平參差不齊,也就是所謂的總口難調,那么講解的插入點就決定了這個講解能否有一個好的效果,這個插入點一定要盡可能多的插入到大家的知識樹上去。最開始的插入點是大家比較熟悉的部分,那么往后的講解就能一氣通貫,反之就是一個接一個的概念,也就是最討厭的用概念講概念,搞得人一頭霧水。

  首先說一下Dependency Property(DP)。這個也有很多人講過了,包括我也經常和人講起。講它的儲存,屬性的繼承,驗證和強制值,反射和值儲存的優先級等。那么為什么要有DP,它能帶來什么好處呢?

  拋開DP,先說一下Property,屬性,用來封裝類的數據的。那么DP,翻譯過來是依賴屬性,也就是說類的屬性要存在依賴,那么這層依賴是怎么來的呢。任意的一個DP,MSDN上的一個實踐是這樣的:

public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register("IsSpinning", typeof(bool)); 

public bool IsSpinning 
{ 
get { return (bool)GetValue(IsSpinningProperty); } 
set { SetValue(IsSpinningProperty, value); } 
} 
View Code

  單看IsSpinning,和傳統的屬性沒什么區別,類型是bool型,有get,set方法。只不過內部的實現分別調用了GetValue和SetValue,這兩個方法是DependecyObject(簡稱DO,是WPF中所有可視Visual的基類)暴露出來的,傳入的參數是IsSpinningProperty。再看IsSpinningProperty,類型就是DependencyProperty,前面用了static readonly,一個單例模式,有DependencyProperty.Register,看起來像是往容器里注冊。

  粗略一看,也就是如此。那么,它真正的創新、威力在哪里呢。拋開它精巧的設計不說,先看儲存。DP中的數據也是存儲在對象中的,每個DependencyObject內部維護了一個EffectiveValueEntry的數組,這個EffectiveValueEntry是一個結構,封裝了一個DependencyProerty的各個狀態值animatedValue(作動畫),baseValue(原始值),coercedValue(強制值),expressionValue(表達式值)。我們使用DenpendencyObject.GetValue(IsSpinningProperty)時,就首先取到了該DP對應的EffectiveValueEntry,然后返回當前的Value。

  那么,它和傳統屬性的區別在哪里,為什么要搞出這樣一個DP呢?第一,內存使用量。我們設計控件,不可避免的要設計很多控件的屬性,高度,寬度等等,這樣就會有大量(私有)字段的存在,一個繼承樹下來,低端的對象會無法避免的膨脹。而外部通過GetValue,SetValue暴露屬性,內部維護這樣一個EffectiveValueEntry的數組,顧名思義,只是維護了一個有效的、設置過值的列表,可以減少內存的使用量。第二,傳統屬性的局限性,這個有很多,包括一個屬性只能設置一個值,不能得到變化的通知,無法為現有的類添加新的屬性等等。

  這里談談DP的動態性,表現有二:可以為類A設置類B的屬性;可以給類A添加新的屬性。這都是傳統屬性所不具備的,那么是什么讓DependencyObject具有這樣的能力呢,就是這個DenpencyProperty的設計。在DP的設計中,對於單個的DP來說,是單例模式,也就是構造函數私有,我們調用DependencyProperty.Register或者DependencyProperty.RegisterAttached這些靜態函數的時候,內部就會調用到私有的DP 的構造函數,構建出新的DP,並把這個DP加入到全局靜態的一個HashTable中,鍵值就是根據傳入時的名字和對象類型的hashcode取異或生成的。

  既然DependencyProperty是維護在一個全局的HashTable中的,那么具體到每個對象的屬性又是怎么通過GetValue和SetValue來和DependencyProperty關聯上的,並獲得PropertyChangeCallback等等的能力呢。在一個DP的注冊方法中,最多傳遞五個參數 :

public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata,

ValidateValueCallback validateValueCallback);

其中第一和第三個參數就是用來確定HashTable中的鍵值,第二個參數確定了屬性的類型,第四個參數是DP中的重點,定義了DP的屬性元數據。在元數據中,定義了屬性變化和強制值的Callback等。那么在一個SetValue的過程中,會出現哪些步驟呢:

  1. 取得該DP下對應這個DependencyObject的PropertyMetadata,這句可能聽起來有些拗口。Metadata,按微軟一般的命名規則,一般是用來描述對象自身數據的,那么一個DP是否只含有一個propertyMetadata呢?答案是不是,一個DP內部維護了一個比較高效的map,里面存儲了多個propertyMetadata,也就是說DP和propertyMetadata是一對多的關系。這里是為什么呢,因為同一個DP可能會被用到不同的DependencyObject中去,對於每類DependencyObject,對這個DP的處理都有所不同,這個不同可以表現在默認值不同,properyMetadata里面的功能不同等等,所以在設計DP的時候設計了這樣一個DP和propertyMetadata一對多的關系。
  2. 取得了該DP下對應真正干活的PropertyMetadata,下一步要真正的”SetValue”了。這個“value”就是要設置的值,設置之后要保存到我們前面提到的EffectiveValueEntry上,所以這里還要先取得這個DP對應的EffectiveValueEntry。在DependencyObject內部的EffectiveValueEntry的數組里面查找這個EffectiveValueEntry,有,取得;沒有,新建,加入到數組中。
  3. 那么這個EffectiveValueEntry到底是用來干什么的,為什么需要這樣一個結構體?如果你對WPF有一定了解,可能會聽說WPF值儲存的優先級,local value>style trigger>template trigger>…。在一個EffectiveValueEntry中,定義了一個BaseValueSourceInternal,用來表示設置當前Value的優先級,當你用新的EffectiveValueEntry更新原有的EffectiveValueEntry時,如果新的EffectiveValueEntry中BaseValueSourceInternal高於老的,設置成功,否則,不予設置。
  4. 剩下的就是proertyMetadata了,當你使用類似如下的參數注冊DP,
public static readonly DependencyProperty CurrentReadingProperty = DependencyProperty.Register( 
"CurrentReading", 
typeof(double), 
typeof(Gauge), 
new FrameworkPropertyMetadata( 
Double.NaN, 
FrameworkPropertyMetadataOptions.AffectsMeasure, 
new PropertyChangedCallback(OnCurrentReadingChanged), 
new CoerceValueCallback(CoerceCurrentReading)), new ValidateValueCallback(IsValidReading));
View Code

  當屬性發生變化的時候,就會調用metadata中傳入的委托函數。這個過程是這樣的, DependencyObject中定義一個虛函數 :

protected virtual void OnPropertyChanged(DependencyPropertyChangedEventArgs e)。

  當DP發生變化的時候就會先首先調用到這個OnPropertyChanged函數,然后如果metaData中設置了PropertyChangedCallback的委托,再調用委托函數。這里我們設置了FrameworkPropertyMetadataOptions.AffectsMeasure, 意思是這個DP變化的時候需要重新測量控件和子控件的Size。具體WPF的實現就是FrameworkElement這個類重載了父類DependencyObject的OnPropertyChanged方法,在這個方法中判斷參數中的metadata是否是FrameworkPropertyMetadata,是否設置了
FrameworkPropertyMetadataOptions.AffectsMeasure這個標志位,如果有的話調用一下自身的InvalidateMeasure函數。

  簡要的談了一下DependencyProperty,除了微軟那種自賣自誇,這個DependencyProperty究竟為我們設計實現帶來了哪些好處呢? 
  1. 就是DP本身帶有的PropertyChangeCallback等等,方便我們的使用。 
  2. DP的動態性,也可以叫做靈活性。舉個例子,傳統的屬性,需要在設計類的時候設計好,你在汽車里拿飛機翅膀肯定是不可以的。可是DependencyObject,通過GetValue和SetValue來模仿屬性,相對於每個DependencyObject內部有一個百寶囊,你可以隨時往里放置數據,需要的時候又可以取出來。當然,前面的例子都是使用一個傳統的CLR屬性來封裝了DP,看起來和傳統屬性一樣需要聲明,下面介紹一下WPF中很強大的Attached Property。

  再談Attached Property之前,我打算和朋友們談一個設計模式,結合項目實際,會更有助於分析DP,這就是MVVM(Mode-View-ViewModel)。關於這個模式,網上也有很多論述,也是我經常使用的一個模式。那么,它有什么特點,又有什么優缺點呢?先來看一個模式應用:

public class NameObject : INotifyPropertyChanged 
{ 
private string _name = "name1"; 
public string Name 
{ 
get 
{ 
return _name; 
} 
set 
{ 
_name = value; 
NotifyPropertyChanged("Name"); 
} 
} 

private void NotifyPropertyChanged(string name) 
{ 
if (PropertyChanged != null) 
{ 
PropertyChanged(this, new PropertyChangedEventArgs(name)); 
} 
} 

public event PropertyChangedEventHandler PropertyChanged; 
} 



public class NameObjectViewModel : INotifyPropertyChanged 
{ 

private readonly NameObject _model; 

public NameObjectViewModel(NameObject model) 
{ 
_model = model; 
_model.PropertyChanged += new PropertyChangedEventHandler(_model_PropertyChanged); 
} 

void _model_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
NotifyPropertyChanged(e.PropertyName); 
} 

public ICommand ChangeNameCommand 
{ 
get 
{ 
return new RelayCommand( 
new Action<object>((obj) => 
{ 

Name = "name2"; 

}), 
new Predicate<object>((obj) => 
{ 
return true; 
})); 
} 
} 

public string Name 
{ 
get 
{ 
return _model.Name; 
} 
set 
{ 
_model.Name = value; 
} 
} 

private void NotifyPropertyChanged(string name) 
{ 
if (PropertyChanged != null) 
{ 
PropertyChanged(this, new PropertyChangedEventArgs(name)); 
} 
} 

public event PropertyChangedEventHandler PropertyChanged; 
} 



public class RelayCommand : ICommand 
{ 
readonly Action<object> _execute; 
readonly Predicate<object> _canExecute; 

public RelayCommand(Action<object> execute, Predicate<object> canExecute) 
{ 
_execute = execute; 
_canExecute = canExecute; 
} 

public bool CanExecute(object parameter) 
{ 
return _canExecute == null ? true : _canExecute(parameter); 
} 

public event EventHandler CanExecuteChanged 
{ 
add { CommandManager.RequerySuggested += value; } 
remove { CommandManager.RequerySuggested -= value; } 
} 

public void Execute(object parameter) 
{ 
_execute(parameter); 
} 
} 



public partial class Window1 : Window 
{ 
public Window1() 
{ 
InitializeComponent(); 
this.DataContext = new NameObjectViewModel(new NameObject()); 
} 
} 



<Window x:Class="WpfApplication7.Window1" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
Title="Window1" Height="300" Width="300"> 
<Grid> 
<TextBlock Margin="29,45,129,0" Name="textBlock1" Height="21" VerticalAlignment="Top" 
Text="{Binding Path=Name}"/> 
<Button Height="23" Margin="76,0,128,46" Name="button1" VerticalAlignment="Bottom" 
Command="{Binding Path=ChangeNameCommand}">Rename</Button> 
</Grid> 
</Window
View Code

類的關系如圖所示:

  這里NameObject -> Model,NameObjectViewModel -> ViewModel,Window1 -> View我們知道,在通常的Model-View世界中,無論MVC也好,MVP也好,包括我們現在提到的MVVM,它的Model和View的功能都類似,Model是用來封裝核心數據,邏輯與功能計算的模型,View是視圖,具體可以對應到窗體(控件)等。那么View的功能主要有,把Model的數據顯示出來,響應用戶的操作,修改Model,剩下Controller或Presenter的功能就是要組織Model和View之間的關系,整理一下Model-View世界中的需求點,大致有: 
  1. 為View提供數據,如何把Model中的數據提供給View。 
  2. Model中的數據發生變化后,View如何更新視圖。 
  3. 根據不同的情況為Model選擇不同的View。 
  4. 如何響應用戶的操作,鼠標點擊或者一些其他的事件,來修改Model。

  所謂時勢造英雄,那么WPF為MVVM打造了一個什么“時勢“呢。 
1. FrameworkElement類中定義了屬性DataContext(數據上下文),所有繼承於FrameworkElement的類都可以使用這個數據上下文,我們在XAML中的使用類似Text=”{Binding Path=Name}”的時候,隱藏的含義就是從這個控件的DataContext(即NameObjectViewModel)中取它的Name屬性。相當於通過DataContext,使View和Model中存在了一種松耦合的關系。 
2. WPF強大的Binding(綁定)機制,可以在Model發生變化的時候自動更新UI,前提是Model要實現INotifyPropertyChanged接口,在Model數據發生變化的時候,發出ProperyChaned事件,View接收到這個事件后,會自動更新綁定的UI。當然,使用WPF的DenpendencyProperty,發生變化時,View也會更新,而且相對於使用INotifyPropertyChanged,更為高效。 
3. DataTemplate和DataTemplateSelector,即數據模板和數據模板選擇器。可以根據Model的類型或者自定義選擇邏輯來選擇不同的View。 
4. 使用WPF內置的Command機制,相對來說,我們對事件更為熟悉。比如一個Button被點擊,一個Click事件會被喚起,我們可以注冊Button的Click事件以處理我們的邏輯。在這個例子里,我使用的是Command="{Binding Path=ChangeNameCommand}",這里的ChangeNameCommand就是DataContext(即NameObjectViewModel)中的屬性,這個屬性返回的類型是ICommand。在構建這個Command的時候,設置了CanExecute和Execute的邏輯,那么這個ICommand什么時候會調用,Button Click的時候會調用么?是的,WPF內置中提供了ICommandSource接口,實現了這個接口的控件就有了觸發Command的可能,當然具體的觸發邏輯要自己來控制。Button的基類ButtonBase就實現了這個接口,並且在它的虛函數OnClick中觸發了這個Command,當然,這個Command已經被我們綁定到ChangeNameCommand上去了,所以Button被點擊的時候我們構建ChangeNameCommand傳入的委托得以被調用。

  正是借助了WPF強大的支持,MVVM自從提出,就獲得了好評。那么總結一下,它真正的亮點在哪里呢? 
1. 使代碼更加干凈,我沒使用簡潔這個詞,因為使用這個模式后,代碼量無疑是增加了,但View和Model之間的邏輯更清晰了。MVVM致力打造一種純凈UI的效果,這里的純凈指后台的xaml.cs,如果你編寫過WPF的代碼,可能會出現過后台xaml.cs代碼急劇膨脹的情況。尤其是主window的后台代碼,動則上千行的代碼,整個window內的控件事件代碼以及邏輯代碼混在一起,看的讓人發惡。 
2. 可測試性。更新UI的時候,只要Model更改后發出了propertyChanged事件,綁定的UI就會更新;對於Command,只要我們點擊了Button,Command就會調用,其實是借助了WPF內置的綁定和Command機制。如果在這層意思上來說,那么我們就可以直接編寫測試代碼,在ViewModel上測試。如果修改數據后得到了propertyChanged事件,且值已經更新,說明邏輯正確;手動去觸發Command,模擬用戶的操作,查看結果等等。就是把UnitTest也看成一個View,這樣Model-ViewModel-View和Model-ViewModel-UnitTest就是等價的。 
3. 使用Attached Behavior解耦事件,對於前面的例子,Button的點擊,我們已經嘗試了使用Command而不是傳統的Event來修改數據。是的,相對與注冊事件並使用,無疑使用Command使我們的代碼更“和諧“,如果可以把控件的全部事件都用Command來提供有多好,當然,控件的Command最多一個,Event卻很多,MouseMove、MouseLeave等等,指望控件暴露出那么多Command來提供綁定不太現實。這里提供了一個Attached Behavior模式,目的很簡單,就是要注冊控件的Event,然后在Event觸發時時候調用Command。類似的Sample如下:

public static DependencyProperty PreviewMouseLeftButtonDownCommandProperty = DependencyProperty.RegisterAttached( 
"PreviewMouseLeftButtonDown", 
typeof(ICommand), 
typeof(AttachHelper), 
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(AttachHelper.PreviewMouseLeftButtonDownChanged))); 

public static void SetPreviewMouseLeftButtonDown(DependencyObject target, ICommand value) 
{ 
target.SetValue(AttachHelper.PreviewMouseLeftButtonDownCommandProperty, value); 
} 

public static ICommand GetPreviewMouseLeftButtonDown(DependencyObject target) 
{ 
return (ICommand)target.GetValue(AttachHelper.PreviewMouseLeftButtonDownCommandProperty); 
} 

private static void PreviewMouseLeftButtonDownChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) 
{ 
FrameworkElement element = target as FrameworkElement; 
if (element != null) 
{ 
if ((e.NewValue != null) && (e.OldValue == null)) 
{ 
element.PreviewMouseLeftButtonDown += element_PreviewMouseLeftButtonDown; 
} 
else if ((e.NewValue == null) && (e.OldValue != null)) 
{ 
element.PreviewMouseLeftButtonDown -= element_PreviewMouseLeftButtonDown; 
} 
} 
} 

private static void element_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) 
{ 
FrameworkElement element = (FrameworkElement)sender; 
ICommand command = (ICommand)element.GetValue(AttachHelper.PreviewMouseLeftButtonDownCommandProperty); 
command.Execute(sender); 
View Code

  這里用到了DependencyProperty.RegisterAttached這個AttachedProperty,關於這個模式,留到下面去講,這段代碼的主要意思就是注冊控件的PreviewMouseLeftButtonDown事件,在事件喚起時調用AttachedProperty傳入的Command。

  那么是不是這個模式真的就這么完美呢,當然不是,MVVM配上WPF自然是如魚得水,不過它也有很多不足,或者不適合使用的場合:

1. 這個模式需要Model-ViewModel,在大量數據的時候為每個Model都生成這樣一個ViewModel顯然有些過。ViewModel之所以得名,因為它要把Model的屬性逐一封裝,來給View提供綁定。

2. Command的使用,前面提到過,實現ICommandSource的接口才具備提供Command的能力,那是不是WPF的內置控件都實現了這樣的接口呢?答案是不是,很少,只有像Button,MenuItem等少數控件實現了這一接口,像我們比較常用ComboBoxItem就沒有實現這一接口。接口沒實現,我們想使用Command的綁定更是無從談起了。這個時候我們要使用Command,就不得不自己寫一個ComboxBoxCommandItem繼承於ComboBoxItem,然后自己實現ICommandSource,並且在Click的時候觸發Command的執行了。看起來這個想法不算太好,那不是要自己寫很多控件,目的就是為了用Command,也太為了模式而模式了。但像Expression Blend,它就是定義了很多控件,目的就是為了使用Command,說起來也奇怪,自己設計的控件,用起來自己還需要封裝,這么多個版本也不添加,這個有點說不過去了。

3. 純UI,就是在控件后台的cs代碼中除了構造函數最多只有一行,this.DataContext = xx; 設置一下數據上下文。當然,我目前的項目代碼大都是這樣的,還是那句話,不要為了模式而模式。那么多的控件event,不管是使用Attached模式還是用一些奇技淫巧用反射來構建出Command,都沒什么必要。目前我的做法就是定義一個LoadedCommand,在這個Command中引用界面上的UI元素,ViewModel拿到這個UI元素后在ViewModel中注冊控件事件並處理。還是第一個優點,這么做只是為了讓代碼更干凈,邏輯更清晰,如果都把各個控件事件代碼都寫在一個xaml.cs中看起來比較混亂。

  談過了MVVM,接下來重點談AttachedProperty,這個是很好很強大的feature,也是WPF真正讓我有不一樣感覺的地方。前面簡單談過了DependencyProperty的原理,很多初接觸WPF的朋友們都會覺得DP很繞,主要是被它的名字和我們的第一直覺所欺騙。如果我們定義了一個DP,MyNameProperty,類型是string的。那么在DependencyObject上,我談過了有個百寶囊,就是EffectiveValueEntry數組,它內部最終儲存MyName的值也是string,這個DependencyProperty(即MyNameProperty)是個靜態屬性,是在你設置讀取這個string的時候起作用的,如何起作用是通過它注冊時定義的propertyMetadata決定的。

  簡單來說就是DependencyObject可以使用DependencyProperty,但兩者沒有從屬關系,你在一個DependencyObject中定義了一個DP,在另一個DependencyObject也可以使用這個DP,你在另一個DependencyObject中寫一個CLR屬性使用GetValue和SetValue封裝這個DP是一樣的。唯一DependencyProperty和DependencyObject有關聯的地方就是你注冊的時候,DP保存在全局靜態DP的Hashtable里的鍵值是通過注冊時的名字和這個DependencyObject的類型的hashcode取異或生成的。但這個鍵值也可以不唯一,DP提供了一個AddOwner方法,你可以為這個DP在全局靜態DP中提供一個新鍵值,當然,這兩個鍵值指向同一個DP。

  既然如此,那么為什么有DependencyProperty.Register和DependencyProperty.RegisterAttached兩種方法注冊DP呢。既然DP只是一個引子,通過GetValue和SetValue,傳入DependencyObject就可以取得存儲在其中EffectiveValueEntry里面的值,這兩個不是一樣的么?恩,原理上是一個,區別在於,前面提到,一個DependencyProperty里面會有多個propertyMetadata,比如說Button定義了一個DP,我們又寫了一個CustomButton,繼承於Button。我們在CustomButton的靜態函數中調用了前面DP的OverrideMetadata函數,DP的OverrideMetadata會涉及到Merge操作,它要把新舊的propertyMetadata合二為一成一個,作為新的propertyMetadata,而這個overrideMetadata過程需要調用時傳入的類型必須是DependencyObject的。DependencyProperty.Register和DependencyProperty.RegisterAttached的區別是前者內部調用了OverrideMetadata而后者沒有,也就意味着Rigister方法只能是DependencyObject調用,而后者可以在任何對象中注冊。

  就這一個區別么?恩,還有的,默認的封裝方法,Register是使用CLR屬性來封裝的,RegisterAttached是用靜態的Get,Set來封裝的。Designer反射的時候,遇到靜態的封裝會智能感知成類似Grid.Column=“2”這樣的方式。這個就類似於非要說菜刀有兩大功能,一是砍菜,二是砍人。你要感到納悶,不是因為菜刀有刀刃么?它會和你解釋,不同不同,砍菜進行了優化,你可以用手握着,砍人犯法,最好飛出去…

  那么為什么微軟要把這個注冊過程分為Register和RegisterAttached兩類呢?就是為了強調Attach這個概念,這個過程就是DependencyObject(相當於銀行金庫,有很多箱子)通過DependencyProperty(相當於開箱子的鑰匙)取得自己箱子里的財寶一樣,當然這些所有的鑰匙有人統一管理(全局的HashTable),你來拿鑰匙的時候還要刁難一下你(通過鑰匙上的附帶的propertyMetadata)檢查一下你的身份啦,你存取東西要發出一些通知啦等等。這個Attach,翻譯過來叫附加,所謂的AttachedProperty(附加屬性),就是說人家可以隨時新配一把鑰匙來你這新開一個箱子,或者拿一把舊鑰匙來你這新開個箱子,誰讓你箱子多呢?

  強調了這么多,只是為了說明一點,這個Attach的能力不是因為你注冊了RegisterAttached才具備的,而是DependencyProperty本身設計就支持的。那么這個設計能為我們開發程序帶來哪些好處呢?

  從繼承和接口實現來說,人們初期階段有些亂用繼承,后來出現了接口,只有明確有IS-A語義的才用繼承,能力方面的用接口來支持。比如飛行,那么一般會定義到一個IFlyable的接口,我們實現這個接口以獲得飛行的能力。那么這個能力的獲得要在類的設計階段繼承接口來獲得,那么作為一個已經成熟的人,我是大雄,我要飛,怎么辦?

AttachedProperty來救你。代碼如下:

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

public interface IFlyHandler 
{ 
void Fly(); 
}

public class DragonFlyViewModel : IFlyHandler 
{ 
public void Fly() 
{ 
MessageBox.Show("送你個竹蜻蜓,飛吧!"); 
} 
}


public class FlyHelper 
{ 
public static readonly DependencyProperty FlyHandlerProperty = 
DependencyProperty.RegisterAttached("FlyHandler", typeof(IFlyHandler), typeof(FlyHelper), 
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnFlyHandlerPropertyChanged))); 

public static IFlyHandler GetFlyHandler(DependencyObject d) 
{ 
return (IFlyHandler)d.GetValue(FlyHandlerProperty); 
} 

public static void SetFlyHandler(DependencyObject d, IFlyHandler value) 
{ 
d.SetValue(FlyHandlerProperty, value); 
} 

public static void OnFlyHandlerPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) 
{ 
FrameworkElement element = target as FrameworkElement; 
if (element != null) 
{ 
IFlyHandler flyHander = e.NewValue as IFlyHandler; 
element.MouseLeftButtonDown += new MouseButtonEventHandler((sender, ex) => 
{ 
if (flyHander != null) 
{ 
flyHander.Fly(); 
} 
}); 
} 
} 
} 

<Window x:Class="WpfApplication7.Window1" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:local="clr-namespace:WpfApplication7" 
Title="Window1" Height="300" Width="300"> 
<Grid> 
<Label Margin="72,58,88,113" Name="label1" Background="Yellow" 
local:FlyHelper.FlyHandler="{Binding}">我叫大雄我不會飛</Label> 
</Grid> 
</Window
View Code

  這是一個最簡單的模式應用,當然,還不是很完美,不過已經可以起飛了。我們在FlyHelper中使用DependencyProperty.RegisterAttached注冊了一個AttachedProperty,在OnFlyHandlerPropertyChanged中訂閱了element的MouseLeftButtonDown事件,事件處理就是”起飛”。這里定義了一個IFlyHandler的接口,使用ViewModel模式,ViewModel去實現這個接口,然后使用local:FlyHelper.FlyHandler="{Binding}"綁定,這里{Binding}沒有寫path,默認綁定到DataContext本身,也就是DragonFlyViewModel上。

  你說什么?你要去追小靜?那一定要幫你。你往腦門上點一下,看,是不是會飛了?怎么樣,戴着竹蜻蜓的感覺很好吧,^_^。大雄欣喜若狂,連聲感謝。不過,這么欺騙一個老實人的感覺不太好,實話實說了吧。你真是有寶物不會用啊,你胸前掛着那是啥?小口袋?百寶囊?那是機器貓的口袋,汽車大炮時空飛船,什么掏不出來啊。哦,你嫌竹蜻蜓太慢了?你等等。

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

private void button1_Click(object sender, RoutedEventArgs e) 
{ 
this.DataContext = new FighterViewModel(); 
} 
} 



public interface IFlyHandler 
{ 
void Fly(); 
} 

public class DragonFlyViewModel : IFlyHandler 
{ 
public void Fly() 
{ 
MessageBox.Show("送你個竹蜻蜓,飛吧!"); 
} 
} 

public class FighterViewModel : IFlyHandler 
{ 
public void Fly() 
{ 
MessageBox.Show("送你駕戰斗機,為了小靜,沖吧!"); 
} 
} 



public class FlyHelper 
{ 
private IFlyHandler _flyHandler; 

public FlyHelper(IFlyHandler handler, FrameworkElement element) 
{ 
_flyHandler = handler; 
element.MouseLeftButtonDown += new MouseButtonEventHandler(element_MouseLeftButtonDown); 
} 

void element_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 
{ 
if (_flyHandler != null) 
{ 
_flyHandler.Fly(); 
} 
} 

private void UpdateFlyHandler(IFlyHandler handler) 
{ 
_flyHandler = handler; 
} 

#region FlyHelper 
public static readonly DependencyProperty FlyHelperProperty = 
DependencyProperty.RegisterAttached("FlyHelper", typeof(FlyHelper), typeof(FlyHelper), 
new FrameworkPropertyMetadata(null)); 

public static FlyHelper GetFlyHelper(DependencyObject d) 
{ 
return (FlyHelper)d.GetValue(FlyHelperProperty); 
} 

public static void SetFlyHelper(DependencyObject d, FlyHelper value) 
{ 
d.SetValue(FlyHelperProperty, value); 
} 
#endregion 

#region FlyHandler 
public static readonly DependencyProperty FlyHandlerProperty = 
DependencyProperty.RegisterAttached("FlyHandler", typeof(IFlyHandler), typeof(FlyHelper), 
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnFlyHandlerPropertyChanged))); 

public static IFlyHandler GetFlyHandler(DependencyObject d) 
{ 
return (IFlyHandler)d.GetValue(FlyHandlerProperty); 
} 

public static void SetFlyHandler(DependencyObject d, IFlyHandler value) 
{ 
d.SetValue(FlyHandlerProperty, value); 
} 

public static void OnFlyHandlerPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) 
{ 
FrameworkElement element = target as FrameworkElement; 
if (element != null) 
{ 
FlyHelper helper = (FlyHelper)element.GetValue(FlyHelperProperty); 
if (helper == null) 
{ 
IFlyHandler handler = e.NewValue as IFlyHandler; 
if (handler != null) 
{ 
helper = new FlyHelper(handler, element); 
element.SetValue(FlyHelperProperty, helper); 
} 
} 
else 
{ 
IFlyHandler handler2 = e.NewValue as IFlyHandler; 
//handler2 may be null, this usually happened when this.DataContext = null, release IFlyHandler. 
helper.UpdateFlyHandler(handler2); 
} 
} 
} 
#endregion 
} 
View Code

  這里就是一個完整的Attached模式,這里我添加了一個新的AttachedProperty,類型是FlyHelper,當local:FlyHelper.FlyHandler="{Binding}"綁定值發生變化時,判斷傳入的這個DependencyObject內是否有FlyHelper對象,沒有,構造一個,然后塞入到這個DependencyObject中去;如果有,則更新FlyHelper內持有的IFlyHandler對象。這個Attached模式的好處在於,這個輔助的Helper對象是在運行時構造的,構造之后塞入到UI對象(DependencyObject)中去,僅是UI對象持有這個引用,UI對象被釋放后這個Helper對象也被釋放。FlyHelper對象用於控制何時”起飛”,至於怎么飛則依賴於IFlyHandler這個接口,這層依賴是在綁定時注入的,而這個綁定最終是運用了DataContext這個數據上下文,和MVVM模式搭配的很完美。這也就是MVVM模式中強調的,也就是唯一的依賴,設置控件的DataContext。

  回顧一下,作於例子中的Label,是不具備“飛行“能力的。這種不具備具體說就是不知道什么時候觸發動作,也不知道觸發了之后該干什么。通過一個Attach模式使它具備了這個能力,而且可以隨時更新動作。簡直達到了一種讓你飛,你就飛的境界,值得為它喝彩。

  鑒於這種動態添加控件的能力,這種模式也被稱為Attached Behavior。在Blend 3中,也加入了Behaviors的支持,很多通用的能力,都可以用Behavior來把它抽出來,比如縮放,DragDrop等等。我沒有具體研究過Blend的Behavior,應該也是這種方法或演變吧。在實際項目中,我也大量使用了MVVM和Attached Behavior,配上CommandBinding,Unit Test,腳本化UIAutomation,以及Prism等框架,對一些比較大型的項目,還是很有幫助的。

  順着DP這條線講下來,還是蠻有味道的。當然,WPF中還有一些比較新的概念,包括邏輯樹和視覺樹,路由事件,Style和Template等等。其實縱看WPF,還是有幾條主線的,包括剛才講到的DP,Threading Model與Dispatcher,視覺樹和依賴它產生的路由,Template和Style等等。那么就回到開頭了,如何學好WPF呢?

  其實寫這篇文章之前,我是經常帶着這疑問的。現在新技術推出的很快,雖說沒什么技術是憑空產生,都是逐漸衍變而來的。可是真學下去也要花成本,那怎么樣才是學好了呢,怎么能融入到項目呢?后來總結了下,我需要了解這么一些情況: 
  1. 這門技術是否成熟,前景如何? 
  2. 擺脫宣傳和炒作,這門技術的優缺點在哪里? 
  3. 希望看到一些對這門技術有整體把握的文章,可以不講細節,主要是幫我理出一個輪廓,最好和我的知識樹連一連。 
  4. 有沒有應用的成功案例。 
 5. 最重要的,呵呵,有沒有可以下載的電子書。

  關於WPF,現在講解的書籍和資料已經蠻多了。隨着.NET Framework的升級,包括性能以及輔助工具的支持也越來越好了。但不得不說,WPF學習的時間成本還是很大的。WPF的設計很重,帶着很濃的設計痕跡,查看WPF的源碼,也許你會有種很熟悉的感覺。這種熟悉不是那種流暢美妙之感,到有種到了項目后期,拿着性能測試去優化,拿着Bug報告亂堵窟窿的感覺。

文本滑動功能實現

      
  #region 構造器
        public TextView()
        {
            InitializeComponent();
            txtFont = (this.Content as Canvas).Children[0] as TextBlock;
            Binding binding = new Binding();
            binding.Source = this.DataContext;
            binding.Path = new PropertyPath("CurrentElement.Src");
            BindingOperations.SetBinding(this, TextProperty, binding);
        }
        #endregion
 
        #region 屬性
        public string Src
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }
 
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Src", typeof(string), typeof(TextView), new UIPropertyMetadata(null, CurrentSrcChanged));
 
        private static void CurrentSrcChanged(object element, DependencyPropertyChangedEventArgs e)
        {
            TextView textView = (TextView)element;
            Canvas canvas = (Canvas)textView.Content;
            TextBlock textBlock = (TextBlock)canvas.Children[0];
            XElement xText = XElement.Load(textView.Src);
            textBlock.Text = xText.Attribute("content").Value.ToString();
            canvas.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString(xText.Attribute("bgcolor").Value.ToString()));
            textBlock.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString(xText.Attribute("fgcolor").Value.ToString()));
            textBlock.FontFamily = (FontFamily)(new FontFamilyConverter().ConvertFromString(xText.Attribute("font").Value.ToString()));
            textBlock.FontSize = Convert.ToInt32(xText.Attribute("size").Value.ToString());
            textBlock.TextWrapping = TextWrapping.Wrap;
        }
        #endregion
 
        #region 定時器實現
        //private void UserControl_Loaded(object sender, RoutedEventArgs e)
        //{
        // System.Threading.Thread thread = new System.Threading.Thread(Scroll);
        // timer.Interval = 50;
        // timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
        // timer.Start();
        // thread.Start();
        //}
 
        //void timer_Elapsed(object sender, ElapsedEventArgs e)
        //{
        // offset++;
        // Scroll();
        //}
 
        //private void Scroll()
        //{
        // Action action;
        // action = ()=>scrollViewer.ScrollToVerticalOffset(offset);
        // Dispatcher.BeginInvoke(action);
        //}
 
        //Timer timer = new System.Timers.Timer();
        //private double offset = 0;
 
        #endregion
 
        #region 動畫實現
        private void CeaterAnimation()
        {
            if (txtFont == null || txtFont.ActualHeight < (this.Content as Canvas).ActualHeight)
            {
                return;
            }
            //創建動畫資源
            Storyboard storyboard = new Storyboard();
 
            //移動動畫
            DoubleAnimationUsingKeyFrames HeightMove = new DoubleAnimationUsingKeyFrames();
            Storyboard.SetTarget(HeightMove, txtFont);
            DependencyProperty[] propertyChain = new DependencyProperty[]
            {
                TextBlock.RenderTransformProperty,
                TransformGroup.ChildrenProperty,
                TranslateTransform.YProperty,
            };
            Storyboard.SetTargetProperty(HeightMove, new PropertyPath("(0).(1)[3].(2)", propertyChain));
            HeightMove.KeyFrames.Add(new EasingDoubleKeyFrame(0, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 2))));
            HeightMove.KeyFrames.Add(new EasingDoubleKeyFrame(-txtFont.ActualHeight, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, (int)(txtFont.ActualHeight / 70)))));
            storyboard.Children.Add(HeightMove);
            storyboard.RepeatBehavior = RepeatBehavior.Forever;
            storyboard.Begin();
 
        }
 
        private void TextBlock_Loaded(object sender, RoutedEventArgs e)
        {
            CeaterAnimation();
        }
 
        TextBlock txtFont;
        #endregion
View Code

 

list和observableCollection的區別

list刪除之后不會通知binding的改變,observableCollection則會通知,比list要厲害一點。

wpf數據綁定為什么只能幫頂屬性而不是字段(摘抄)

C#屬性是域的擴展(即通常說的成員變量或字段等)它配合C#中的域(字段)使用,使之構造一個安全的應用程序,為什么說通過屬性來替代域會提高應用程序的安全呢?

    原因就在於C#屬性通過訪問器(Accessors)用進行數據訪問.所以C#的屬性可以設置為只讀或只寫. 而字段卻沒有這樣的功能(只可設置只讀).我們都知道在程序中有時我們是不允許用戶修改一些屬性的,比如地球是圓的。原則上我們是不能修改此屬性.那么我們就可以通過一個屬性來實現這樣的功能.讓它設置為只讀屬性.

    屬性的特點:C#屬性是對類中的字段(fields)的保護,像訪問字段一樣來訪問屬性。同時也就封裝了類的內部數據。每當賦值運算的時候自動調用set訪問器,其他時候則調用get訪問器。 以 帕斯卡命名 不能冠以Get/Set。靜態屬性是通過類名調用的!

    前面我們說到屬性是字段的擴展,我們都知道如果要在類外部訪問字段,就要公開(Public)這個成員字段。但是如果我們真的這樣做了,那這個成員字段的就可以被任意訪問(包括修改,讀取).那怎么辦呢? 用一個屬性就可以解決這個問題.

C#屬性是通過Get(讀取)、Set(設置)來訪問屬性的. 

public class Test 
{ 
    public Test() 
       { 
              // 
// TODO: 在此處添加構造函數邏輯 
// 
        } 
    //為了說明問題這里我們用中文 
    public string 地球的形狀; 
} 
在上面的例子里"地球的形狀"這個字段就可以任意的訪問,不受任何的束縛.但是我們都知道地球是圓的,是不允許修改的一個特性,那怎么辦呢?用一個屬性就可以輕松的解決這個問題. 
public class Test 
{ 
    public Test() 
       {         
              // 
// TODO: 在此處添加構造函數邏輯 
// 
       } 
    //為了說明問題這里我們用中文 
    private string 地球的形狀="";//私有的成員變量,它對於外部是不可見的. 
    public string 地球形狀 
        { 
           get 
           { 
              return 地球的形狀;//這里將私有成員變量地球的形狀返回給"地球的形狀" 
           } 
        } 
} 
這里我們只可以讀取屬性"地球形狀",而不可以寫,如果強制寫編譯器就會提示出錯.這樣我們就可以通過類來訪問屬性. 
Test MyTt=new Test();//實例化類 
string MyTemp=MyTt.地球形狀;//讀取類的屬性 
下面我們說一下寫的應用. 
public class Test 
{ 
    public Test() 
       { 
              // 
// TODO: 在此處添加構造函數邏輯 
// 
        } 
    //為了說明問題這里我們用中文 
    private string 你的名字;//私有的成員變量,它對於外部是不可見的. 
    public string 名字 
        { 
           get 
           { 
              return 你的名字;//這里將私有成員變量"你的名字"的形狀返回給"名字" 
           } 
           set 
           { 
               你的名字=value;//這里的value將等於"名字" 這個屬性值
           } 
        } 
} 
View Code

 

這樣我們就可以對屬性讀取和寫了. 

Test MyTt=new Test();//實例化類 
MyTt.名字="Simon"//設置屬性 
String MyTemp=MyTt.名字;讀取屬性值 
通過上面的例子我們可以看出屬性只不過是做了一個中介的角色而已,真正工作的還是字段(域),但這樣做可以更面向對象,寫出更安全的應用程序。

     C#提供了一個處理此概念的更清爽的方式。在C#中,get和set方法是內在的,而在Java和C++里則需人為維護。C#的處理方式有諸多優點。它鼓勵程序員按照屬性的方式去思考—把這個屬性標為可讀寫的和只讀的哪個更自然?或者根本不應該為屬性?如果你想改變你的屬性的名稱,你只要檢查一處就可以了。

    C#中屬性這種機制使得在保證封裝性的基礎上實現了訪問私有成員的便捷性。一個支持屬性的語言將有助於獲得更好的抽象。

    來自MSDN中的內容:

屬性和屬性過程

    可以使用屬性和字段在對象中存儲信息。屬性使用屬性過程控制如何設置或返回值,而字段只是公共變量。屬性過程是在屬性定義中聲明的代碼塊,可用於在設置或檢索屬性值時執行代碼。

    具有兩種類型的屬性過程:用於檢索屬性值的 Get 屬性過程和用於為屬性分配值的 Set 屬性過程。例如,存儲銀行帳戶余額的屬性可能會在 Get 屬性過程中使用代碼以在返回可用余額之前記入利息並檢查服務費。然后,您可以使用 Set 屬性過程驗證余額並防止它以不正確的方式更新。簡而言之,屬性過程允許對象保護和驗證自己的數據。

只讀和只寫屬性
    大多數屬性都具有 Get 和 Set 屬性過程,這兩個屬性過程可用於讀取和修改存儲在內部的值。然而,您可以使用 ReadOnly 或 WriteOnly 修飾符來限制對屬性進行修改或讀取。

    只讀屬性不能具有 Set 屬性過程。這種屬性用於需要公開但不允許修改的項。例如,可以使用只讀屬性來提供計算機處理器的速度。
    只寫屬性不能具有 Get 屬性過程,它們用於使用不應或不能存儲在對象中的數據配置對象。例如,只寫屬性可用於獲取密碼並在不存儲該密碼的情況下更改對象的狀態。

wpf的command傳遞參數給viewmodel

path是指定名字控件的一個依賴屬性。傳遞的是一個字符串,如果是一個整形的話,還需要將其轉換成為string。
同時需要這個dll,
並且引用如下命名空間。
使用方式如下:
<Controls:DataPager x:Name="dataPager" PageSize="25" Grid.Row="1" TotalCount="{Binding Path=SchedualTotalModel.Total}">
            <I:Interaction.Triggers>
                <I:EventTrigger EventName="PageChanged">
                    <I:InvokeCommandAction Command="{Binding PageChangeCommand}" CommandParameter="{Binding ElementName=dataPager,Path=PageIndex,Converter={StaticResource userConverter}}" />
                </I:EventTrigger>
                <I:EventTrigger EventName="Loaded">
                    <I:InvokeCommandAction Command="{Binding PageSizeCommand}" CommandParameter="{Binding ElementName=dataPager,Path=PageSize,Converter={StaticResource userConverter}}"/>
                </I:EventTrigger>
            </I:Interaction.Triggers>
        </Controls:DataPager>
View Code

 

wpf 事件聚合器

首先要有個事件類:
public class QueueSearchConEvent : CompositePresentationEvent<SearchCon>
    {
 
    }
 
    public class SearchCon
    {
       public string _partyId;
       public string _consultionId;
       public string _doctorId;
       public string _stationSelectedIndex;
       public string _triageSelectedIndex;
       public string _patientName;
       public SearchCon(string partyId, string consultionId, string doctorId, string stationSelectedIndex, string triageSelectedIndex, string patientName)
        {
            this._partyId = partyId;
            this._consultionId = consultionId;
            this._doctorId = doctorId;
            this._stationSelectedIndex = stationSelectedIndex;
            this._triageSelectedIndex = triageSelectedIndex;
            this._patientName = patientName;
        }
   
    }
 
View Code

其次,在構造器之中注冊一下事件:

#region 構造器
        public QueueListViewModel()
        {
            if (!IsInDesignMode)
            {
                _commandParameters = this.UnityContainer.Resolve<CommandParameters>();
                TrigeService = this.UnityContainer.Resolve<ITrigeService>();
                _regionManager = this.UnityContainer.Resolve<IRegionManager>();
                _container = this.UnityContainer.Resolve<IUnityContainer>();
                PageChange(1, 2);
                this.EventAggregator.GetEvent<QueueSearchConEvent>().Subscribe(ScreeningResults);
            }
        }
        #endregion
View Code

最后,發布事件,也就是用事件(觸發某個方法,來引用。可以在項目的任何模塊):

private void SelectButtonChanged(string _partyId, string _consultionId, string _doctorId)
        {
 
            this.EventAggregator.GetEvent<QueueSearchConEvent>().Publish(new SearchCon(_partyId, _consultionId, _doctorId, _stationSelectedIndex, _triageSelectedIndex, _patientName));
        }
View Code

 

wpf中*和auto的區別,以及兩者和滾動條的區別

Auto 表示自動適應顯示內容的寬度, 如自動適應文本的寬度,文本有多長,控件就顯示多長.

* 則表示按比例來分配寬度.

 

<ColumnDefinition Width="3*" />
<ColumnDefinition Width="7*" />

同樣,行可以這樣定義

<RowDefinition Height="3*" />
<RowDefinition Height="7*" />

這些數字可以是小數.
如果數字缺省,則默認是1.
在這個例子中, 列2的寬度是列1的1.5倍.

<ColumnDefinition Width="1.5*" />
<ColumnDefinition />

Auto和*可以混合使用. 在這個例子中,后兩行的寬度在前兩行分配完之后,按比例獲取剩余的寬度.

<Grid.ColumnDefinitions>
    <ColumnDefinition Width="Auto" />  <!-- Auto-fit to content, 'Hi' -->
    <ColumnDefinition Width="50.5" />  <!-- Fixed width: 50.5 device units) -->
    <ColumnDefinition Width="69*" />   <!-- Take 69% of remainder -->
    <ColumnDefinition Width="31*"/>    <!-- Take 31% of remainder -->
</Grid.ColumnDefinitions>
<TextBlock Text="Hi" Grid.Column="0" />

 

auto之后,如果縮小控件顯現不出滾動條,而*則會顯現出來滾動條。

用WPF實現查找結果高亮顯示(摘抄)

概述

我們經常會遇到這樣的需求:到數據庫里查找一些關鍵字,把帶這些關鍵字的記錄返回顯示在客戶端上。但如果僅僅是單純地把文本顯示出來,那很不直觀,用戶不能很輕易地看到他們想找的內容,所以通常我們還要做到“高亮顯示”。

如果是用BS架構去實現,應該很簡單,把相應的關鍵字加上一些label,然后給label定樣式即可,或者直接用js在客戶端渲染,減少服務器的負擔,但CS架構就相對麻煩一點,我這里用WPF寫了一個demo,實現了這個功能的演示:

另外本demo還包括了一些非常有用的wpf的小技巧。

功能介紹

由於這只是一個簡單的DEMO,我和以往的風格一樣,把它做成了“零配置”,我用一個csv文件和LINQ to Object來取代DBMS,執行一些簡單的查詢操作。

查詢方式分為兩種,一種是Full Match,表示全字符匹配,另一種是Any Key,表示用空格斷開查詢字符串,逐個關鍵字查詢。

這個程序的顯示區域使用了ListView控件,之所以使用ListView而不是DataGrid,主要是ListView能很輕易地自適應行高,而DataGrid的行高是固定的,但如果你要換DataGrid去做的話,應該也是同一個道理。

高亮顯示功能分析與實現

要實現高亮顯示,我們可以這么做:在界面上放置一個TextBlock,叫tbTest,然后執行下面的代碼:

tbTest.Inlines.Clear();
tbTest.Inlines.Add( new Run("The"){ Background = Brushes.Yellow });
tbTest.Inlines.Add( " quick brown fox jumps over ");
tbTest.Inlines.Add( new Run("the") { Background = Brushes.Yellow });
tbTest.Inlines.Add( new Run(" lazy dog."));
View Code

就能看到這樣的“高亮”效果:

遺憾的是Inlines這個屬性並非“依賴屬性”(Dependency Property),你不能輕易把一個字符串或對象“綁定”給它。我的做法是創建一個用戶控件,其中只包含一個TextBlock:

<UserControl x:class="HighlightDispDemo.HighlightTextBlock"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <TextBlock Name="innerTextBlock" TextWrapping="Wrap">
    </TextBlock>
</UserControl>
View Code

再給它增加一個叫“HlContent”的依賴屬性,其類型為自定義的HighlightContent:

        public static readonly DependencyProperty HighlightContentProperty = DependencyProperty .Register( "HlContent", 
      typeof(HighlightContent), 
      typeof( HighlightTextBlock), 
      new FrameworkPropertyMetadata( null, OnHtContentChanged));

        [ Description("獲取或設置高亮顯示的內容")]
        [ Category("Common Properties")]
        public HighlightContent HlContent
        {
            get { return(HighlightContent)GetValue( HighlightContentProperty); }
            set { SetValue( HighlightContentProperty, value); }
        }
View Code

HighlightContent的定義如下:

    public enum HighlightContentMode
    {
        FullMatch,
        AnyKey
    };

    public class HighlightContent
    {
        public string Content { get; set; }
        public static string ToHighlight { get; set; }
        public static HighlightContentMode Mode { get; set; }
    }
View Code

 

其中ToHighlight屬性表示要高亮顯示的“鍵”,而Mode屬性則用來指明用“Full Match”還是“Any Key”模式,考慮到同一時間只有一種高亮顯示,我把這兩個屬性定義為static。

“HlContent”的內容變更通知回調函數:

        private static void OnHtContentChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
        {
            if(sender is HighlightTextBlock)
            {
                HighlightTextBlock ctrl = sender as HighlightTextBlock ;
                HighlightContent content = ctrl.HlContent ;
                ctrl.innerTextBlock.Inlines.Clear();
                if(content != null)
                {
                    ctrl.innerTextBlock.Inlines.AddRange(MakeRunsFromContent( content));
                }
            }
        }

        private static IEnumerable<Run> MakeRunsFromContent(HighlightContent content)
        {
             //此函數功能是:將要顯示的字符串根據key及mode,拆分成不同的Run片段
             //代碼較多,從略
        }
View Code

這樣一來,我們就可以用自定義的HighlightTextBlock來取代Textblock實現綁定了。

綁定到ListView(摘抄)

ListView的默認的Column是肯定不支持“高亮”顯示的了,現在我們來自定義Template:

        <ListView ItemContainerStyle="{DynamicResource CustomListViewItemStyle}" Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Name="lvContent" AlternationCount="2">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn Header="OS Name" Width="100">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <hld:HighlightTextBlock HlContent="{Binding Path=OsName,Converter={StaticResource converterHlContent}}"></hld:HighlightTextBlock>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                        <GridViewColumn Header="File System" Width="150">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <hld:HighlightTextBlock HlContent="{Binding Path=FileSystem,Converter={StaticResource converterHlContent}}"></hld:HighlightTextBlock>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                        <GridViewColumn Header="Desktop" Width="200">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <hld:HighlightTextBlock HlContent="{Binding Path=Desktop,Converter={StaticResource converterHlContent}}"></hld:HighlightTextBlock>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>
View Code

可以看到,Template中使用了前面我們自定義的HighlightTextBlock控件,它們綁定的Path分別是OsName,FileSystem和Desktop,其實這都是string,而HlContent需要的是HighlightContent類型,所以我們還得指定一個轉換器,轉換器代碼如下:

    [ValueConversion(typeof(string), typeof(HighlightContent))]
    public class HlContentConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return new HighlightContent {Content = (string)value};
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
View Code

 

使用CsvHelper來讀取CSV文件(摘抄)

這次我沒有使用DBMS,其實DEMO項目能不用DBMS就不用了,否則部署困難,不利於問題分析。CsvHelper可以從github上獲取,地址是:https://github.com/JoshClose/CsvHelper

它的幫助寫得稍微有點潦草(個人感覺),我這里稍稍補充說明下:CsvHelper的思路就是把csv文件轉為一個可枚舉的集合,其中的一行轉為集合中的一個對象,那么一列就對應到這個對象的一個屬性,那么究竟哪一列轉為那個屬性呢?我們得告訴它,這就是“Map”,了解了這個之后看一下下面的代碼,一切都清楚了。

    public class LinuxInfo
    {
        public string OsName { get; set; }
        public string FileSystem { get; set; }
        public string Desktop { get; set; }
    }

    public class LinuxMap : CsvClassMap<LinuxInfo>
    {
        public override void CreateMap()
        {
            Map(m => m.OsName).Index(0);
            Map(m => m.FileSystem).Index(1);
            Map(m => m.Desktop).Index(2);
        }
    }
View Code

上面代碼是對象及Map定義。下面是執行讀取和轉換的操作。

TextReader tr = new StreamReader("linux.csv", Encoding.UTF8);
     CsvReader csv = new CsvReader(tr);
     csv.Configuration.RegisterClassMap<LinuxMap>();
     csv.Configuration.HasHeaderRecord = false; //表示csv文件不帶header行
     _listData = csv.GetRecords<LinuxInfo>().ToList();
View Code

 

ListView的隔行背景樣式(摘抄)

把ListView的AlternationCount屬性設為2,並指定ItemContainerStyle="{DynamicResource CustomListViewItemStyle}"。Style這樣定義:

     <Style x:Key="CustomListViewItemStyle" TargetType="{x:Type ListViewItem}">
          <Style.Triggers>
               <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                    <Setter Property="Background" Value="#DDEEFF"></Setter>
               </Trigger>
          </Style.Triggers>
     </Style>
View Code

 

讓TextBox獲得焦點時全選文本(摘抄)

這個功能得在App.xml.cs中做一些全局處理:

 
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            //為了讓TextBox能夠在獲得焦點的時候自動選中其中文本,特意添加此全局事件處理
            EventManager.RegisterClassHandler(typeof(TextBox), UIElement.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(SelectivelyHandleMouseButton), true);
            EventManager.RegisterClassHandler(typeof(TextBox), UIElement.GotKeyboardFocusEvent, new RoutedEventHandler(SelectAllText), true);
        }

        private static void SelectivelyHandleMouseButton(object sender, MouseButtonEventArgs e)
        {
            var textbox = (sender as TextBox);
            if (textbox != null && !textbox.IsKeyboardFocusWithin)
            {
                if (e.OriginalSource.GetType().Name == "TextBoxView")
                {
                    e.Handled = true;
                    textbox.Focus();
                }
            }
        }

        private static void SelectAllText(object sender, RoutedEventArgs e)
        {
            var textBox = e.OriginalSource as TextBox;
            if (textBox != null)
                textBox.SelectAll();
        }
View Code

完整代碼下載

HighlightDispDemo.7z(Visual Studio 2010)

使用WPF中GridViewColumn的CellTemplateSelector(摘抄)

 GridViewColumn中的CellTemplateSelector使用起來有一點需要注意:DataTemplateSelector(數據模板選擇器)的操作對象是ListView的直接綁定對象,而不是對象在該列的屬性。事實上如果設置了DisplayMemberBinding,CellTemplateSelector和CellTemplate都是無效的。

  這個問題從一個簡單的示例上可以看出來,這樣一個簡單的對象:

    
public class Item

    {

        public int Int { get; set; }

        public bool Bool { get; set; }

    }

 

  用ListView綁定一系列的Item對象,然后分兩列顯示這個Int和Bool。Bool那列肯定顯示True或者False(因為沒定義CellTemplateSelector或者CellTemplate的話,WPF默認顯示對象的ToString)。現在用一個DataTemplateSelector,來根據Bool是True或者False,顯示一個紅框或者綠框。

  此時DataTemplateSelector你有可能這樣定義(錯誤的):

class BoolSelector : DataTemplateSelector

    {

        public DataTemplate TrueTemplate { get; set; }

        public DataTemplate FalseTemplate { get; set; }

 

        public override DataTemplate SelectTemplate(object item, DependencyObject container)

        {

            //錯誤代碼

            if ((bool)item)

                return TrueTemplate;

            return FalseTemplate;

        }

    }

 

  然后在XAML中,定義好這個DataTemplateSelector,設置DisplayMemberBinding和CellTemplateSelector:

    
<Window.Resources>

        <loc:BoolSelector x:Key="sel">

            <loc:BoolSelector.TrueTemplate>

                <DataTemplate>

                    <Rectangle Fill="Green" Width="20" Height="20"/>

                </DataTemplate>

            </loc:BoolSelector.TrueTemplate>

            <loc:BoolSelector.FalseTemplate>

                <DataTemplate>

                    <Rectangle Fill="Red" Width="20" Height="20"/>

                </DataTemplate>

            </loc:BoolSelector.FalseTemplate>

        </loc:BoolSelector>

    </Window.Resources>

    <ListView Name="list">

        <ListView.View>

            <GridView>

                <GridView.Columns>

                    <GridViewColumn Header="int" DisplayMemberBinding="{Binding Int}"/>

                    <GridViewColumn Header="bool" DisplayMemberBinding="{Binding Bool}" CellTemplateSelector="{StaticResource sel}" />

                </GridView.Columns>

            </GridView>

        </ListView.View>

    </ListView>

 

  最后ListView仍然只輸出True和False。

  原因是如果設置了GridViewColumn的DisplayMemberBinding,CellTemplateSelector和CellTemplate都是無效的。同樣CellTemplateSelector的對應DataTemplateSelector的針對對象是數據綁定對象(本例中的Item類),而不是對象的屬性(本例中的Bool值)。

  所以首先修改上面的DataTemplateSelector的SelectTemplate,讓其針對Item對象:

        
public override DataTemplate SelectTemplate(object item, DependencyObject container)

        {

            if (((Item)item).Bool)

                return TrueTemplate;

            return FalseTemplate;

        }

  接着不使用GridViewColumn的DisplayMemberBinding。直接用CellTemplateSelector:

<GridViewColumn Header="bool" CellTemplateSelector="{StaticResource sel}" />

  結果才會顯示正確:

  

 

  本文來自劉圓圓的博客,原文地址:http://www.cnblogs.com/mgen/archive/2011/11/24/2262465.html

wpf窗體樣式

重寫窗體樣式,現在我接觸的,主要是下面這個方法,它有如下幾個步驟:

1、寫一個繼承自window的ResourceDictionary,並且要設置如下的style
<Setter Property="WindowStyle" Value="None" />
<Setter Property="AllowsTransparency" Value="True" />
 
2、grid布局窗體,添加最大化最小化關閉等按鈕。並且在繼承window的類中實現這些按鈕的功能,這些功能也繼承自window自帶的最大最小化和關閉。
    如何讓此類能訪問ResourceDictionary中的按鈕,只需要將其作為app.xaml中的資源文件引用。
      引用如下:
 <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resource Dictionaries/MacStyledWindow.xaml" />
                <ResourceDictionary Source="Resources/TabItemResource.xaml"/>
                <ResourceDictionary Source="Resources/WindowRegionResource.xaml"/>
                <ResourceDictionary Source="Resources/Styles.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
    調用方法如下:
            ControlTemplate baseWindowTemplate = (ControlTemplate)App.Current.Resources["MacWindowTemplate"];
            //set up background image
            Button skinBtn = (Button)baseWindowTemplate.FindName("SkinBtn", this);
            skinBtn.Click += delegate
            {
                Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
                if (dlg.ShowDialog() == true)
                {
                    ImageBrush brush = (ImageBrush)baseWindowTemplate.FindName("MyBgImg",this);
 
                    BitmapImage bitmap = new BitmapImage();
                    bitmap.BeginInit();
                    bitmap.UriSource = new Uri(dlg.FileName, UriKind.Absolute);
                    bitmap.EndInit();
                    brush.ImageSource = bitmap;
                }
            };
View Code

 

3、重寫windows調整大小的方法,利用win32程序實現。
   此部分主要重寫的DragMove、顯示拖拉鼠標形狀、還原鼠標形狀、判斷區域,改變窗體大小幾個方面。
 
MediaElement循環播放問題

xaml中的代碼:
<MediaElement Name="myMediaElement" Stretch="Fill" UnloadedBehavior="Manual" Source="{Binding CurrentElement.Src}" MediaEnded="myMediaElement_MediaEnded">
    </MediaElement>
 
后台的事件處理:
        
private void myMediaElement_MediaEnded(object sender, RoutedEventArgs e)
        {
            MediaElement me = (MediaElement)sender;
            me.Position = new TimeSpan(0);
            me.Play();
        }

 

其中,如果不設置UnloadedBehavior="Manual"屬性的話,后台處理當播放完畢的時候不能繼續進行播放。也就是到me.Play();

 處會報錯誤。

用一個程序打開另一個程序C#

//打開! 

        private void ventuze_Click(object sender, RoutedEventArgs e)

        {

            //獲取當前窗口句柄

            IntPtr handle = new WindowInteropHelper(this).Handle;

            string path = @"D:\twinflag_res\Ventuz\New.vpr";

            string openSoft = @"C:\Program Files\VentuzPro\VentuzPresenter.exe";

            app = System.Diagnostics.Process.Start(openSoft,path);

            prsmwh = app.MainWindowHandle;

            while (prsmwh == IntPtr.Zero)

            {

                prsmwh = app.MainWindowHandle;

            }

            //設置父窗口

            SetParent(prsmwh, handle);

            ShowWindowAsync(prsmwh, 3);//子窗口最大化

        }

 

//關閉

private void Window_Closed(object sender, System.EventArgs e)

  {

   if (app.CloseMainWindow()){

    app.Kill();

    app.Close();

   } 
View Code

 

WPF WPF中解決內存泄露的幾點提示與解決方法(摘抄)

一直以來用WPF做一個項目,但是開發中途發現內存開銷太大,用ANTS Memory Profiler分析時,發現在來回點幾次載入頁面的操作中,使得非托管內存部分開銷從起始的43.59M一直到150M,而托管部分的開銷也一直持高不下,即每次申請的內存在結束后不能完全釋放。在網上找了不少資料,甚受益,現在修改后,再也不會出現這種現象了(或者說,即使有也不嚇人),寫下幾個小心得:

1. 慎用WPF樣式模板合並

  我發現不采用合並時,非托管內存占用率較小,只是代碼的理解能力較差了,不過我們還有文檔大綱可以維護。

2. WPF樣式模板請共享

  共享的方式最簡單不過的就是建立一個類庫項目,把樣式、圖片、筆刷什么的,都扔進去,樣式引用最好使用StaticResource,開銷最小,但這樣就導致了一些寫作時的麻煩,即未定義樣式,就不能引用樣式,哪怕定義在后,引用在前都不行。

3. 慎用隱式類型var的弱引用

  這個本來應該感覺沒什么問題的,可是不明的是,在實踐中,發現大量采用var與老老實實的使用類型聲明的弱引用對比,總是產生一些不能正確回收的WeakRefrense(這點有待探討,因為開銷不是很大,可能存在一些手工編程的問題)

4. 寫一個接口約束一下

  誰申請誰釋放,基本上這點能保證的話,內存基本上就能釋放干凈了。我是這么做的:

    interface IUIElement : IDisposable
{
/// <summary>
/// 注冊事件
/// </summary>
void EventsRegistion();

/// <summary>
/// 解除事件注冊
/// </summary>
void EventDeregistration();
}

在實現上可以這樣:

 1 #region IUIElement 成員
2 public void EventsRegistion()
3 {
4 this.traineeReport.SelectionChanged += new SelectionChangedEventHandler(traineeReport_SelectionChanged);
5 }
6
7 public void EventDeregistration()
8 {
9 this.traineeReport.SelectionChanged -= new SelectionChangedEventHandler(traineeReport_SelectionChanged);
10 }
11
12 private bool disposed;
13
14 ~TraineePaymentMgr()
15 {
16 ConsoleEx.Log("{0}被銷毀", this);
17 Dispose(false);
18 }
19
20 public void Dispose()
21 {
22 ConsoleEx.Log("{0}被手動銷毀", this);
23 Dispose(true);
24 GC.SuppressFinalize(this);
25 }
26
27 protected void Dispose(bool disposing)
28 {
29 ConsoleEx.Log("{0}被自動銷毀", this);
30 if(!disposed)
31 {
32 if(disposing)
33 {
34 //托管資源釋放
35 ((IDisposable)traineeReport).Dispose();
36 ((IDisposable)traineePayment).Dispose();
37 }
38 //非托管資源釋放
39 }
40 disposed = true;
41 }
42 #endregion
 比如寫一個UserControl或是一個Page時,可以參考以上代碼,實現這樣接口,有利於資源釋放。

5. 定時回收垃圾

DispatcherTimer GCTimer = new DispatcherTimer();
public MainWindow()
{
InitializeComponent();
this.GCTimer.Interval = TimeSpan.FromMinutes(10); //垃圾釋放定時器 我定為每十分鍾釋放一次,大家可根據需要修改
  this.GCTimer.start();

this.EventsRegistion(); // 注冊事件
}

public void EventsRegistion()
{
this.GCTimer.Tick += new EventHandler(OnGarbageCollection);
}

public void EventDeregistration()
{
this.GCTimer.Tick -= new EventHandler(OnGarbageCollection);
}

void OnGarbageCollection(object sender, EventArgs e)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
6. 較簡單或可循環平鋪的圖片用GeometryDrawing實現

一個圖片跟幾行代碼相比,哪個開銷更少肯定不用多說了,而且這幾行代碼還可以BaseOn進行重用。

<DrawingGroup x:Key="Diagonal_50px">
<DrawingGroup.Children>
<GeometryDrawing Brush="#FF2A2A2A" Geometry="F1 M 0,0L 50,0L 50,50L 0,50 Z"/>
<GeometryDrawing Brush="#FF262626" Geometry="F1 M 50,0L 0,50L 0,25L 25,0L 50,0 Z"/>
<GeometryDrawing Brush="#FF262626" Geometry="F1 M 50,25L 50,50L 25,50L 50,25 Z"/>
</DrawingGroup.Children>
</DrawingGroup>
這邊是重用
<DrawingBrush x:Key="FrameListMenuArea_Brush" Stretch="Fill" TileMode="Tile" Viewport="0,0,50,50" ViewportUnits="Absolute"
Drawing="{StaticResource Diagonal_50px}"/>

上面幾行代碼相當於這個:

7. 使用Blend做樣式的時候,一定要檢查完成的代碼

眾所周知,Blend定義樣式時,產生的垃圾代碼還是比較多的,如果使用Blend,一定要檢查生成的代碼。

 

8. 靜態方法返回諸如List<>等變量的,請使用out

比如

public static List<String> myMothod()

{...}

請改成

public static myMothod(out List<String> result)

{...}

 

9. 打針對此問題的微軟補丁

3.5的應該都有了吧,這里附上NET4的內存泄露補丁地址,下載點這里 (QFE:  Hotfix request to implement hotfix KB981107 in .NET 4.0 )

這是官方給的說明,看來在樣式和數據綁定部分下了點工夫啊:

  1. 運行一個包含樣式或模板,請參閱通過使用 StaticResource 標記擴展或 DynamicResource 標記擴展應用程序資源的 WPF 應用程序。 創建使用這些樣式或模板的多個控件。 但是,這些控件不使用引用的資源。 在這種情況的一些內存WeakReference對象和空間泄漏的控股數組后,垃圾回收釋放該控件。
  2. 運行一個包含的控件的屬性是數據綁定到的 WPF 應用程序DependencyObject對象。 該對象的生存期是超過控件的生存期。 許多控件時創建,一些內存WeakReference對象和容納數組空格被泄漏后垃圾回收釋放該控件。
  3. 運行使用樹視圖控件或控件派生於的 WPF 應用程序,選擇器類。 將控件注冊為控制中的鍵盤焦點的內部通知在KeyboardNavigation類。 該應用程序創建這些控件的很多。 例如對於您添加並刪除這些控件。 在本例中為某些內存WeakReference對象和容納數組空格被泄漏后垃圾回收釋放該控件。

繼續更新有關的三個8月補丁,詳細的請百度:KB2487367  KB2539634  KB2539636,都是NET4的補丁,在發布程序的時候,把這些補丁全給客戶安裝了會好的多。

10.  對string怎么使用的建議

這個要解釋話就長了,下面僅給個例子說明一下,具體的大家去找找MSDN

        string ConcatString(params string[] items)
{
string result = "";
foreach (string item in items)
{
result += item;
}
return result;
}

string ConcatString2(params string[] items)
{
StringBuilder result = new StringBuilder();
for(int i=0, count = items.Count(); i<count; i++)
{
result.Append(items[i]);
}
return result.ToString();
}
建議在需要對string進行多次更改時(循環賦值、連接之類的),使用StringBuilder。我已經把工程里這種頻繁且大量改動string的操作全部換成了StringBuilder了,用ANTS Memory Profiler分析效果顯著,不僅提升了性能,而且垃圾也少了。

 

11. 其它用上的技術暫時還沒想到,再補充...

 

如果嚴格按以上操作進行的話,可以得到一個滿意的結果:

運行了三十分鍾,不斷的切換功能,然后休息5分鍾,回頭一看,結果才17M左右內存開銷,效果顯著吧。

然后對於調試信息的輸出,我的做法是在窗體應用程序中附帶一個控制台窗口,輸出調試信息,給一個類,方便大家:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Trainee.UI.UIHelper
{
    public struct COORD
    {
        public ushort X;
        public ushort Y;
    };

    public struct CONSOLE_FONT
    {
        public uint index;
        public COORD dim;
    };

    public static class ConsoleEx
    {
        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32", CharSet = CharSet.Auto)]
        internal static extern bool AllocConsole();

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32", CharSet = CharSet.Auto)]
        internal static extern bool SetConsoleFont(IntPtr consoleFont, uint index);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32", CharSet = CharSet.Auto)]
        internal static extern bool GetConsoleFontInfo(IntPtr hOutput, byte bMaximize, uint count, [In, Out] CONSOLE_FONT[] consoleFont);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32", CharSet = CharSet.Auto)]
        internal static extern uint GetNumberOfConsoleFonts();

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32", CharSet = CharSet.Auto)]
        internal static extern COORD GetConsoleFontSize(IntPtr HANDLE, uint DWORD);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32.dll ")]
        internal static extern IntPtr GetStdHandle(int nStdHandle);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        internal static extern int GetConsoleTitle(String sb, int capacity);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("user32.dll", EntryPoint = "UpdateWindow")]
        internal static extern int UpdateWindow(IntPtr hwnd);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("user32.dll")]
        internal static extern IntPtr FindWindow(String sClassName, String sAppName);

        public static void OpenConsole()
        {
            var consoleTitle = "> Debug Console";
            AllocConsole();


            Console.BackgroundColor = ConsoleColor.Black;
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.WindowWidth = 80;
            Console.CursorVisible = false;
            Console.Title = consoleTitle;
            Console.WriteLine("DEBUG CONSOLE WAIT OUTPUTING...{0} {1}\n", DateTime.Now.ToLongTimeString());

            try
            {
                //這里是改控制台字體大小的,可能會導致異常,在我這個項目中我懶得弄了,如果需要的的話把注釋去掉就行了
                //IntPtr hwnd = FindWindow(null, consoleTitle);
                //IntPtr hOut = GetStdHandle(-11);

                //const uint MAX_FONTS = 40;
                //uint num_fonts = GetNumberOfConsoleFonts();
                //if (num_fonts > MAX_FONTS) num_fonts = MAX_FONTS;
                //CONSOLE_FONT[] fonts = new CONSOLE_FONT[MAX_FONTS];
                //GetConsoleFontInfo(hOut, 0, num_fonts, fonts);
                //for (var n = 7; n < num_fonts; ++n)
                //{
                //    //fonts[n].dim = GetConsoleFontSize(hOut, fonts[n].index);
                //    //if (fonts[n].dim.X == 106 && fonts[n].dim.Y == 33)
                //    //{
                //        SetConsoleFont(hOut, fonts[n].index);
                //        UpdateWindow(hwnd);
                //        return;
                //    //}
                //}
            }
            catch
            {

            }
        }

        public static void Log(String format, params object[] args)
        {
            Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] " + format, args);
        }
        public static void Log(Object arg)
        {
            Console.WriteLine(arg);
        }
    }
}
View Code

在程序啟動時,可以用ConsoleEx.OpenConsole()打開控制台,用ConsoleEx.Log(.....)或者干脆用Console.WriteLine進行輸出就可以了。

wpf中的畫線問題,后台接受數據InkCanvas

后台操作代碼:
/// <summary>
    /// MainWindow.xaml 的交互邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DispatcherTimer timer = new DispatcherTimer();
            timer.Interval = new TimeSpan(10000);
            timer.Tick += new EventHandler(timer_Tick);
            timer.Start();
        }
 
        void timer_Tick(object sender, EventArgs e)
        {
            main();
            inv.Strokes = strokeCollection;
        }
 
        double i = 0, j = 0;
        StrokeCollection strokeCollection = new StrokeCollection();
 
        private void main()
        {
            StylusPoint stylusPoint = new StylusPoint(i,j);
            StylusPoint stylusPoint1 = new StylusPoint(i++, j++);
            StylusPointCollection stylusPointCollection = new System.Windows.Input.StylusPointCollection();
            stylusPointCollection.Add(stylusPoint);
            stylusPointCollection.Add(stylusPoint1);
            DrawingAttributes drawingAttributes = new DrawingAttributes();
            drawingAttributes.Color = Color.FromRgb(33,111,0);
            Stroke stroke = new Stroke(stylusPointCollection, drawingAttributes);
           
            strokeCollection.Add(stroke);
        }
    }
View Code

前台xaml代碼:

<Window x:Class="WpfApplication7.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"> 
<Canvas x:Name="canvas">
        <InkCanvas x:Name="inv" Margin="0"/>
    </Canvas>
</Window>

 

wpf的binding大圖片程序內存崩潰的大圖片問題解決方法

  binding圖片的高低像素就可以了,不用講圖片全部都賦值過去,這樣看起來和之前像素也是一樣。
如此binding之后就不會出現崩潰的現象了
  
public class ImageViewModel : BaseRegionViewModel
    {
        #region 構造器
        public ImageViewModel()
        {
        }
 
        #endregion
 
        #region 屬性
 
        private BitmapImage _myBitmapImage;
       
        public BitmapImage MyBitmapImage
        {
            get { return _myBitmapImage; }
            set
            {
                _myBitmapImage = value;
                this.RaisePropertyChanged("MyBitmapImage");
            }
        }
 
        #endregion
 
        public override void CurrentElementChanged()
        {
            base.CurrentElementChanged();
            BitmapImage bitmap = new BitmapImage();
            bitmap.BeginInit();
            bitmap.StreamSource = new FileStream(CurrentElement.Src, FileMode.Open, FileAccess.Read);
            bitmap.DecodePixelHeight = Convert.ToInt32(CurrentRegion.Height);
            bitmap.DecodePixelWidth = Convert.ToInt32(CurrentRegion.Width);
            bitmap.CacheOption = BitmapCacheOption.OnLoad;
            bitmap.EndInit();
 
            bitmap.StreamSource.Dispose();
 
            MyBitmapImage = bitmap;
        }
 
        public override void ClearResource()
        {
            base.ClearResource();
        }
    }
View Code

 

wpf有時候按鈕透明之后會出現虛框(tab鍵之后會出現)解決方法

<Style TargetType="Button" x:Key="btnHotelInfoStyle" >
            <Setter Property="Focusable" Value="false"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border Name="border"
                    BorderThickness="0"
                    Padding="4,2"
                    BorderBrush="DarkGray"
                    CornerRadius="0"
                    Background="{TemplateBinding Background}">
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

 

wpf 的converter傳遞復雜參數(摘抄)

Step 1

在WPF的C#代碼文件中給定義復雜類型的變量,並給其賦值;

Sample code: List<User>lsUser=。。。。

Setp 2

在 C#代碼對應的XAML 中將此復雜參數定義為資源;

Sample code:

<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:c="clr-namespace:SDKSample"
  x:Class="SDKSample.Window1"
  Width="400"
  Height="280"
  Title="MultiBinding Sample">
        
  <Window.Resources>
    <c:lsUser x:Key="userList"/>
...
 
</Window.Resources>
 
這里的命名空間 C 是你的復雜參數所在的命名空間;
Step 3

 

  <UserControl.Resources>

    <app:UserManager x:Key="StaticUsers"/>

    <app:UserNameConverter  x:Key="UsrConverter"/>

  </UserControl.Resources>

<TextBox  Text="{Binding XXXX,Converter={StaticResource UsrConverter},

  ConverterParameter={StaticResource userList }}" />

 

Step 4 Converter 里對參數的使用

 

public class UserNameConverter : IValueConverter

{

    public object IValueConverter.Convert(object value, Type targetType,object parameter, CultureInfo culture)

    {

      List<User> usrs = parameter as List<User>;

      ...

    }

}
View Code

 

一、 如果 ObservableCollection<User> 是基礎數據,可以將它們作為全局的變量,在 UserNameConverter 中直接使用 ObservableCollection<User> ,用不着使用 ConverterParameter。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public  class  UserNameConverter : IValueConverter 
     public  object  IValueConverter.Convert( object  value, Type targetType, object  parameter, CultureInfo culture)
    
       // UserManager.Instance.Users 存儲了全局的 ObservableCollection<User>
       // 也可以實現一些方法,用於將“12,34,56”返回“張三,李四,王五”
       return  UserManager.Instance.GetMultiNamesFromIds( value.ToString() );
     }
 
     public  object  IValueConverter.ConvertBack( object  value, Type targetType,  object  parameter, CultureInfo culture)
     {
       return  null ;
     }
}



二、 如果 ObservableCollection<User> 不能作為全局的,個人推薦的方式是避開使用將 ObservableCollection<User> 作為 ConverterParameter 的方法,改為使用字符串或 enum
參數,如

1
2
3
4
5
6
7
8
9
public  class  UserNameConverter : IValueConverter 
     public  object  IValueConverter.Convert( object  value, Type targetType, object  parameter, CultureInfo culture)
     {
       // UserManager.Instance 中存儲了多個 ObservableCollection<User> ,根據參數返回不同的 ObservableCollection<User>
       // parameter 是一個字符串或者是一個 enum
       return  UserManager.Instance.GetMultiNamesFromIds( parameter.ToString(), value.ToString() );
     }
}



三、 如果 ObservableCollection<User> 不能作為全局的,而又非要把它通過ConverterParameter 來傳遞, 由於在 XAML 中只能使用 StaticResources ,那么就只能在代碼中來給 StaticResources 賦值了:

1
2
3
4
5
6
   < UserControl.Resources >
     < app:UserManager  x:Key = "StaticUsers" />
     < app:UserNameConverter   x:Key = "UsrConverter" />
   </ UserControl.Resources >
< TextBox   Text="{Binding XXXX,Converter={StaticResource UsrConverter},
   ConverterParameter={StaticResource StaticUsers}}" />



1
2
3
4
5
6
7
8
public  class  UserNameConverter : IValueConverter 
     public  object  IValueConverter.Convert( object  value, Type targetType, object  parameter, CultureInfo culture)
     {
       ObservableCollection<User> usrs = parameter  as  ObservableCollection<User>;
       ...
     }
}



1
2
3
4
public  class  UserManager : ObservableCollection<User>
{
 
}



在 UserControl 的 .cs 代碼中(一般在是構造函數中):

1
2
UserManager =  this .Resources[ "StaticUsers" as  UserManager ;
UserManager.Add(User 實例);



四、 個人喜好是不會使用上述 “三”的 UserManager 定義,
不使用繼承,而是使用組合

1
2
3
4
public  class  UserManager 
{
    public   ObservableCollection<User> UserList{ get ; set ;}
}

 

1
2
3
4
5
6
   < UserControl.Resources >
     < app:UserManager  x:Key = "StaticUsers" />
     < app:UserNameConverter   x:Key = "UsrConverter" />
   </ UserControl.Resources >
< TextBox   Text="{Binding XXXX,Converter={StaticResource UsrConverter},
   ConverterParameter={StaticResource StaticUsers.UserList}}" />


不過好像又記得在 StaticResource 中是不能使用 屬性路徑 的,即 StaticUsers.UserList 是不能用的,
忘了啦。

PS:以上代碼全手寫,僅作思路參考之用。

 

Sorry , 發完貼又看了一遍,上述第三步有誤,不用在 XAML 中定義 StaticUsers 的,在.cs代碼中定義即可:

1
2
3
4
5
   < UserControl.Resources >
     < app:UserNameConverter   x:Key = "UsrConverter" />
   </ UserControl.Resources >
< TextBox   Text="{Binding XXXX,Converter={StaticResource UsrConverter},
   ConverterParameter={StaticResource StaticUsers}}" />

 

1
2
3
4
5
//在構造函數的 InitializeComponent(); 之前執行
ObservableCollection<User> usrs = ... 通過各種途徑得到 ObservableCollection<User> ;
this .Resources.Add( "StaticUsers" , usrs );
 
InitializeComponent();
其實我自己幾天前已經用靜態變量搞定了,不過這樣就沒用到ConverterParameter了
微軟搞了一個ConverterParameter,我想學習用一下這個東東而已,而且覺得靜態變量不是很好那種...

   public class IUserNameConverter : IValueConverter
    {
       public static ObservableCollection<User> TempUserList;

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
        }
    }

------------------------------

恩,還是sunpire厲害啊

我上網找了半天,就是沒有找到
this.Resources.Add("StaticUsers", usrs );
這一句關鍵代碼啊

不過可惜的是這句代碼一定要在構造函數中寫,問題是實際代碼沒看到過這樣寫的,
InitializeComponent()肯定早運行過了,
實際業務數據肯定是從服務器去取的,我總不能為了搞這個,從服務器取業務數據都寫在InitializeComponent前面吧

也就是說ConverterParameter更多的只具有觀賞價值,沒有太多實用價值。

簡單的說,復雜一點的轉換器參數傳遞最好使用全局靜態變量,避免使用ConverterParameter這個東西。

恩,明白,結貼。
WPF color、brush、string轉換(摘抄)

//color轉為brush:  
Brush br = new SolidColorBrush(Color.FromRgb(000));   
//string轉Color  
(Color)ColorConverter.ConvertFromString((string)str);  
//Color轉string((Color)value).ToString();  
string和Brush的轉換  
Brush color = newSolidColorBrush((Color)ColorConverter.ConvertFromString((string)str));  
//Brush轉string  
((Brush)value).ToString();  
//string轉byte[]  
System.Text.UnicodeEncoding converter = newSystem.Text.UnicodeEncoding();  
byte[] stringBytes = converter.GetBytes(inputString);   
//byte[]轉string  
System.Text.UnicodeEncoding converter = newSystem.Text.UnicodeEncoding();  
stringoutputString = converter.GetString(stringByte);   
    
1.由string的rgb數值"255,255,0"轉換為color  
  
{  
  string[]  color_params = e.Parameter.ToString().Split(',');  
  byte color_R = Convert.ToByte(color_params[0]);  
  byte color_G = Convert.ToByte(color_params[1]);  
  byte color_B = Convert.ToByte(color_params[2]);  
}  
  
2.由顏色名稱字符串("black") 轉化為color  
  
 {  
  
  //ColorConverter c = new ColorConverter();  
  //object obj = c.ConvertFrom();  
  //Color color = (Color)obj;  
  Color color = Color.FromRgb(color_R, color_G, color_B);    
  
}  
  
3.將blend的 8位顏色值轉為color  
  
        /// <summary>  
  
        /// 將blend的8位顏色值轉為color  
        /// </summary>  
        /// <param name="colorName"></param>  
        /// <returns></returns>  
        public Color ToColor(string colorName)  
        {  
            if (colorName.StartsWith("#"))  
                colorName = colorName.Replace("#", string.Empty);  
            int v = int.Parse(colorName, System.Globalization.NumberStyles.HexNumber);  
            return new Color()  
            {  
                A = Convert.ToByte((v >> 24) & 255),  
                R = Convert.ToByte((v >> 16) & 255),  
                G = Convert.ToByte((v >> 8) & 255),  
                B = Convert.ToByte((v >> 0) & 255)  
            };  
        }  
View Code

 

 
wpf后台binding

public partial class ImageView : UserControl
    {
        ImageViewModel imageViewModel;
        public ImageView()
        {
            InitializeComponent();
            imageViewModel = this.DataContext as ImageViewModel;
            BindingBitmap();
        }
 
        private void BindingBitmap()
        {
            Binding binding = new Binding();
            binding.Source = imageViewModel;
            binding.Path = new PropertyPath("MyBitmapImage");
            BindingOperations.SetBinding(this.img, Image.SourceProperty, binding);
        }
    }   

 

自定義豎排textblock,前人基礎(摘抄)

public class VerticalTextBlock : Control 
{ 
public VerticalTextBlock() 
{ 
IsTabStop = false; 
var templateXaml = 
@"<ControlTemplate " + 
#if SILVERLIGHT 
"xmlns='http://schemas.microsoft.com/client/2007' " + 
#else 
"xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' " + 
#endif 
"xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>" + 
"<Grid Background=\"{TemplateBinding Background}\">" + 
"<TextBlock x:Name=\"TextBlock\" TextAlignment=\"Center\"/>" + 
"</Grid>" + 
"</ControlTemplate>"; 
#if SILVERLIGHT 
Template = (ControlTemplate)XamlReader.Load(templateXaml); 
#else 
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(templateXaml))) 
{ 
Template = (ControlTemplate)XamlReader.Load(stream); 
} 
#endif 
} 

public override void OnApplyTemplate() 
{ 
base.OnApplyTemplate(); 
_textBlock = GetTemplateChild("TextBlock") as TextBlock; 
CreateVerticalText(_text); 
} 

private string _text { get; set; } 
private TextBlock _textBlock { get; set; } 

public string Text 
{ 
get { return (string)GetValue(TextProperty); } 
set { SetValue(TextProperty, value); } 
} 
public static readonly DependencyProperty TextProperty = DependencyProperty.Register( 
"Text", typeof(string), typeof(VerticalTextBlock), new PropertyMetadata(OnTextChanged)); 
private static void OnTextChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) 
{ 
((VerticalTextBlock)o).OnTextChanged((string)(e.NewValue)); 
} 
private void OnTextChanged(string newValue) 
{ 
CreateVerticalText(newValue); 
} 

private void CreateVerticalText(string text) 
{ 
_text = text; 
if (null != _textBlock) 
{ 
_textBlock.Inlines.Clear(); //清楚一遍數據,防止兩條消息來臨的時候發生錯誤
bool first = true; 
foreach (var c in _text) 
{ 
if (!first) 
{ 
_textBlock.Inlines.Add(new LineBreak()); 
} 
_textBlock.Inlines.Add(new Run { Text = c.ToString() }); 
first = false; 
} 
} 
} 

}
View Code

 

wpf中Button按鈕和Enter關聯(原+摘)

1.MouseButtonEventArgs 類定義在 System.Windows.Input命名空間中。含有方法GetPosition方法,此方法返回一個Point類型(這是定義在System.Windows命名空間內的結構體類型)的對象,表示鼠標坐標(相對於GetPosition參數的左上角)。含有ChangedButton屬性,此屬性“獲取以該事件關聯的按鈕”(也就是是鼠標的左鍵,右鍵等)。

2.Button的 IsDefault 屬性設置為true表明此按鈕和Enter鍵關聯;IsCancel 屬性設置為true表明此按鈕和Esc鍵關聯。

如果給Button設置快捷鍵則可以如下:

<Button Name="btnEnter" Content="查詢(_Q)" Width="200" Height="100" ></Button> 下划線加字母代表快捷鍵為Alt+Q。

但是如果Button在ToolBar里那么上面這種寫法不行,須寫為:

<ToolBar>

     <Button Name="btnWantCreate">

          <AccessText>新增(_N)</AccessText>

     </Button>

</ToolBar>

3.圖片屬性里德生成操作設置為SplashScreen,這樣在顯示自己程序前會先顯示這個圖片。

4.WPF中. 在Toolbar中連續使用Tab鍵時,其會循環與其中.如何跳出該循環呢, 很簡單, 將TabBar的TabNavigation屬性設置為Continue就可以了。

<ToolBar  KeyboardNavigation.TabNavigation="Continue"></ToolBar>

5.在XAML里限定泛型的類型(http://msdn.microsoft.com/zh-cn/library/ms750476.aspx):

      假定聲明了以下 XAML 命名空間定義:

xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib"

     List<字符串>

     <scg:List x:TypeArguments="sys:String" ...> 實例化新的帶有兩個 String 類型參數的 List<T>。

 Dictionary<字符串,字符串>

 <scg:Dictionary x:TypeArguments="sys:String,sys:String" ...> 實例化新的帶有兩個 String 類型參數的 Dictionary<TKey, TValue>。

6.程序的Icon設置:  右鍵項目選擇屬性-->應用程序-->資源-->圖標和清單-->圖標  選中自己想要設置的Icon

Windows Presentation Foundation (WPF) 獨立應用程序具有兩種類型的圖標:

一個程序集圖標,此圖標是在應用程序的項目生成文件中使用 <ApplicationIcon> 屬性指定的。 此圖標用作程序集的桌面圖標。

注意:When debugging in Visual Studio, your icon may not appear due to the hosting process. If you run the executable, the icon will appear. 有關更多信息,請參見承載進程 (vshost.exe)。當調試的時候不顯示圖標,開始運行不調試時才顯示.

每個窗口各自具有的一個圖標,此圖標是通過設置 Icon 指定的。 對於每個窗口,此圖標在窗口的標題欄、任務欄按鈕和 Alt-Tab 應用程序選擇列表項中使用.

WPF 窗口總是顯示一個圖標。 如果沒有通過設置 Icon 提供圖標,WPF 將基於下列規則選擇要顯示的圖標:

1. 使用程序集圖標(如果已指定)。

2. 如果未指定程序集圖標,則使用默認的 Microsoft Windows 圖標。

如果使用 Icon 指定自定義窗口圖標,可以通過將 Icon 設置為 null 來還原默認的應用程序圖標。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM