Styel在英文中解釋爲」樣式「,在Web開發中,css爲層疊樣式表,自從.net3.0推出WPF以來,WPF也有樣式一說,經過設置樣式,使其WPF控件外觀更加美化同時減小了大量的複雜屬性的設置。css
在WPF中,設置外觀樣式咱們有不少種方式,好比經過設置控件的屬性來控制控件的外觀樣式;或者經過在每個控件中分別設置Style;或者經過在整個Window.Resource中設置Style,又或者在App.xaml的Application.Resource設置Style。html
在此咱們就不討論第一種方式設置控件的外觀了,由於這不涉及到Style的使用。那麼後三種設置樣式來控制控件的外觀有什麼區別呢?那麼咱們來分別討論吧!linux
第一,經過在每個控件中分別設置Style來控制控件的外觀,示例代碼以下:git
<Button Content="Button" Height="23" Name="button3" Width="75">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Black" />
</Style>
</Button.Style>
</Button>程序員
以上樣式的設置只正對當前的Button有效,與其餘同種類型的控件無關。github
第二,經過在Window.Resource中設置Style來控制控件的外觀,示例代碼以下:數據庫
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Background" Value="LightBlue" />
</Style>
</Window.Resources>express
以上樣式的設置,針對整個Window的全部Button有效(只要沒有單獨的對Button設置),這種方法呢,相對於第一種來講減小了代碼量。同時修改起來出錯的可能性較小!編程
第三,經過在App.xaml中的Application.Resource中設置Style來控制控件的外觀,示例代碼以下:canvas
<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>
以上樣式的設置,針對整個Application的全部Button有效(只要在Window.Resource或者獨立的Button中沒有分別設置),這種方法來設置控件的外觀樣式呢,相對於前兩種來講,代碼量有大幅的增長,同時呢,在一個應用程序中,每每同種類型的控件的不少屬性都是相同的,咱們在Applicaiton.Resource進行全局設置,使其維護更加方便!
好了,以上是我對WPF中Style的理解,但願在我總結的同時,可以給同行們提供幫助,若是發現錯誤,請積極指正,謝謝!
若是隻是單純的讓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>
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);
<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"); } }
<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>
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 }}" />
public class UserNameConverter : IValueConverter { public object IValueConverter.Convert(object value, Type targetType,object parameter, CultureInfo culture) { List<User> usrs = parameter as List<User>; ... } }
如何學好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); } }
單看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的過程當中,會出現哪些步驟呢:
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));
當屬性發生變化的時候,就會調用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
類的關係如圖所示:
這裏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);
這裏用到了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
這是一個最簡單的模式應用,固然,還不是很完美,不過已經能夠起飛了。咱們在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 }
這裏就是一個完整的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
list刪除以後不會通知binding的改變,observableCollection則會通知,比list要厲害一點。
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將等於"名字" 這個屬性值 } } }
這樣咱們就能夠對屬性讀取和寫了.
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 屬性過程,它們用於使用不該或不能存儲在對象中的數據配置對象。例如,只寫屬性可用於獲取密碼並在不存儲該密碼的狀況下更改對象的狀態。
<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>
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; } }
其次,在構造器之中註冊一下事件:
#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
最後,發佈事件,也就是用事件(觸發某個方法,來引用。能夠在項目的任何模塊):
private void SelectButtonChanged(string _partyId, string _consultionId, string _doctorId) { this.EventAggregator.GetEvent<QueueSearchConEvent>().Publish(new SearchCon(_partyId, _consultionId, _doctorId, _stationSelectedIndex, _triageSelectedIndex, _patientName)); }
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以後,若是縮小控件顯現不出滾動條,而*則會顯現出來滾動條。
咱們常常會遇到這樣的需求:到數據庫裏查找一些關鍵字,把帶這些關鍵字的記錄返回顯示在客戶端上。但若是僅僅是單純地把文本顯示出來,那很不直觀,用戶不能很輕易地看到他們想找的內容,因此一般咱們還要作到「高亮顯示」。
若是是用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."));
就能看到這樣的「高亮」效果:
遺憾的是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>
再給它增長一個叫「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); } }
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; } }
其中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片斷 //代碼較多,從略 }
這樣一來,咱們就能夠用自定義的HighlightTextBlock來取代Textblock實現綁定了。
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>
能夠看到,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(); } }
此次我沒有使用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); } }
上面代碼是對象及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();
把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>
這個功能得在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(); }
完整代碼下載
HighlightDispDemo.7z(Visual Studio 2010)
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:
結果纔會顯示正確:
本文來自劉圓圓的博客,原文地址:http://www.cnblogs.com/mgen/archive/2011/11/24/2262465.html
重寫窗體樣式,如今我接觸的,主要是下面這個方法,它有以下幾個步驟:
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; } };
private void myMediaElement_MediaEnded(object sender, RoutedEventArgs e) { MediaElement me = (MediaElement)sender; me.Position = new TimeSpan(0); me.Play(); }
處會報錯誤。
//打開! 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(); }
一直以來用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
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();
}
一個圖片跟幾行代碼相比,哪一個開銷更少確定不用多說了,並且這幾行代碼還能夠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 )
這是官方給的說明,看來在樣式和數據綁定部分下了點工夫啊:
繼續更新有關的三個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();
}
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); } } }
在程序啓動時,能夠用ConsoleEx.OpenConsole()打開控制檯,用ConsoleEx.Log(.....)或者乾脆用Console.WriteLine進行輸出就能夠了。
後臺操做代碼: /// <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); } }
前臺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>
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(); } }
<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>
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>; ... } }
1、 若是 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
;
}
}
|
2、 若是 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() );
}
}
|
3、 若是 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 實例);
|
4、 我的喜愛是不會使用上述 「三」的 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();
|
//color轉爲brush: Brush br = new SolidColorBrush(Color.FromRgb(0,0,0)); //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) }; }
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); } }
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; } } } }
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 來還原默認的應用程序圖標。