WPF自定义控件 —— 装饰器
摘自:http://www.cnblogs.com/Curry/archive/2009/09/16/1567757.html
顧名思義就是裝飾用的,也就是說(shuō)不改變?cè)械目丶Y(jié)構(gòu),但可以為控件添加一些新的功能,或是為控件的顯示外觀(guān)增加些東西。如MSDN中的例子:
本來(lái)TextBox四角沒(méi)有圓點(diǎn),但是通過(guò)裝飾器可以為它加上。所以可以看成在TextBox上加了層。
這樣就“無(wú)痛”的給控件進(jìn)行了裝飾。當(dāng)然應(yīng)用不單單這樣加幾個(gè)點(diǎn)而已,修飾嘛比如拖動(dòng)控件的修飾
?
而之前比較著名的層拖拽是Bea StollinitzHow can I drag and drop items between data bound ItemsControls?
?
?
一.AdornerLayer
我們說(shuō)層,是覆蓋在控件上的一層?xùn)|西,那么控件上能不能覆蓋多個(gè)層呢?
答案當(dāng)然是可以的,而這些層自然的要放在一個(gè)容器中,這個(gè)容器就叫做AdornerLayer
然后問(wèn)題又來(lái)了這個(gè)層是如何產(chǎn)生的?是我們?nèi)藶榉诺?#xff0c;還是自動(dòng)產(chǎn)生的(雖然自動(dòng)實(shí)際上也是需要有人寫(xiě)的)?
我們知道AdornerLayer有個(gè)方法
public static AdornerLayer GetAdornerLayer(Visual visual);可以得到某個(gè)Visual的所在的層,我們打開(kāi)Reflector進(jìn)行查看
?
public static AdornerLayer GetAdornerLayer(Visual visual) {if (visual == null){throw new ArgumentNullException("visual");}for (Visual visual2 = VisualTreeHelper.GetParent(visual) as Visual; visual2 != null; visual2 = VisualTreeHelper.GetParent(visual2) as Visual){if (visual2 is AdornerDecorator){return ((AdornerDecorator)visual2).AdornerLayer;}if (visual2 is ScrollContentPresenter){return ((ScrollContentPresenter)visual2).AdornerLayer;}}return null; }很容易我們就可以看出它實(shí)際是通過(guò)可視樹(shù)進(jìn)行查找,然后判斷元素是否為AdornerDecorator或ScrollContentPresenter,如果是的話(huà)則取他們的AdornerLayer屬性,也就是說(shuō)AdornerLayer是由AdornerDecorator或ScrollContentPresenter產(chǎn)生的,打開(kāi)本地MSDN ,鍵入ScrollContentPresenter
?
由紅框中的文字可以得知ScrollContentPresenter屬于ScrollViewer,也就是說(shuō)有ScrollViewer的地方就會(huì)有AdornerLayer,打開(kāi)ScrollViewer的鏈接我們又可以了解到ScrollViewer通常需要包裝Panel控件
那么哪些控件默認(rèn)樣式是用到ScrollViewer的呢,據(jù)我所知繼承于ItemsControl的控件,還有Window等常用控件等,當(dāng)然這里就不一一列舉了。
如果實(shí)在沒(méi)有ScrollViewer的地方,或者需要直接在控件上加層,我們也可以手動(dòng)在控件外面包個(gè)AdornerDecorator來(lái)產(chǎn)生AdornerLayer。
<AdornerDecorator><TextBox Text="可以得到AdornerLayer"/> </AdornerDecorator>?
?
那么AdornerLayer到底是種什么概念,為什么總會(huì)在控件之上呢?
再用Reflector打開(kāi)ScrollContentPresenter或AdornerDecorator在GetVisualChild(int index)中應(yīng)該會(huì)注意到下面的代碼(下面代碼在ScrollContentPresenter中獲得)
private readonly AdornerLayer _adornerLayer = new AdornerLayer();protected override Visual GetVisualChild(int index) {if (base.TemplateChild == null){throw new ArgumentOutOfRangeException("index", index, SR.Get("Visual_ArgumentOutOfRange"));}switch (index){case 0:return base.TemplateChild;case 1:return this._adornerLayer;}throw new ArgumentOutOfRangeException("index", index, SR.Get("Visual_ArgumentOutOfRange")); }?
這里就很明白了,index 0通常是我們需要裝飾的控件,1就是AdornerLayer。我們知道系統(tǒng)首先會(huì)畫(huà)0層的東西,再畫(huà)1層 ,導(dǎo)致1永遠(yuǎn)都在0上。所以其實(shí)AdornerLayer也存在于可視樹(shù),可以通過(guò)VisualTreeHelper來(lái)找到。而且你不管調(diào)整控件的z-index都是無(wú)用的,人家寫(xiě)死了嘛。
?
二.Adorner
????? 有了容器,自然的要往里面添加?xùn)|西,要不然不是空空如也么,有了也等于沒(méi)有。而AdornerLayer規(guī)定能夠加入它這個(gè)容器的只能是Adorner的派生類(lèi)。在此淫威下所以我們也不得不臣服,把類(lèi)繼承于A(yíng)dorner這個(gè)抽象類(lèi)。
public class SimpleTextBlockAdorner : Adorner {private TextBlock _textBlock;public SimpleTextBlockAdorner(UIElement adornedElement): base(adornedElement) {_textBlock = new TextBlock();_textBlock.Foreground = Brushes.Green;_textBlock.Text = "AdornerText";}protected override Visual GetVisualChild(int index){return _textBlock;}protected override int VisualChildrenCount{get{return 1;}}protected override Size ArrangeOverride(Size finalSize){//為控件指定位置和大小_textBlock.Arrange(new Rect(new Point(-10, 20), _textBlock.DesiredSize));return base.ArrangeOverride(finalSize);} }CS中代碼如下:
public Window1() {InitializeComponent();TextBlock textBlock = new TextBlock();textBlock.FlowDirection = FlowDirection.RightToLeft;textBlock.Text = "FlowDirection.RightToLeft";//放一個(gè)層容器AdornerDecorator adornerDecorator = new AdornerDecorator();adornerDecorator.Child = textBlock;this.Content = adornerDecorator;//得到層容器var adornerLayer = AdornerLayer.GetAdornerLayer(textBlock);//在層容器中加層adornerLayer.Add(new SimpleTextBlockAdorner(textBlock)); }?
當(dāng)改變textBlock的FlowDirection屬性時(shí),會(huì)出現(xiàn)如下所示的結(jié)果。也就是說(shuō)被裝飾的元素的FlowDirection效果對(duì)層上的元素有影響。
?
?
探其究竟便又要用到Reflector了
原來(lái)是做了綁定,不過(guò)我一直未探明白的是DispatcherPriority的順序意義是什么,到底適用在哪些場(chǎng)景,還望高手多多指點(diǎn)。
?
其次我們關(guān)心的另外一件事是,這個(gè)層有多大?是覆蓋整個(gè)被裝飾的控件還是整個(gè)屏幕或是整個(gè)窗口。一般MeasureOverride可以指定控件的大小,順序是Measure->Arrange->Render(本想貼我布局中的那張圖,可回頭看有些地方居然是錯(cuò)的,不知道當(dāng)時(shí)怎么想的,囧RZ)。那么來(lái)看看MeasureOverride到底干了什么?
從紅線(xiàn)框中我們很容易看出他是用了裝飾控件的呈現(xiàn)尺寸來(lái)修飾的。當(dāng)然這只是默認(rèn),你也可以自己重載MeasureOverride來(lái)指定大小。
?
可要是裝飾控件(本文中TextBox)的大小改變了,裝飾器(本文中的SimpleTextBlockAdorner)怎么偵測(cè)的到?
實(shí)際上當(dāng)我們改變裝飾控件的大小時(shí)候,多是改變控件的Height或Width, 以改變Height為例,他是產(chǎn)生于FrameworkElement中的,定義如下
public static readonly DependencyProperty HeightProperty = DependencyProperty.Register("Height", typeof(double), _typeofThis, new FrameworkPropertyMetadata((double) 1.0 / (double) 0.0,?FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(FrameworkElement.OnTransformDirty)), new ValidateValueCallback(FrameworkElement.IsWidthHeightValid));
就是說(shuō)改變Height的時(shí)候會(huì)觸發(fā)Measure方法,而Measure方法會(huì)沿可視樹(shù)向上找到父容器(在本例中是AdornerDecorator),然后調(diào)用它的OnChildDesiredSizeChanged方法,而OnChildDesiredSizeChanged中調(diào)用的是父容器本身的Measure方法,Measure方法會(huì)重新改變子容器的大小,裝飾控件(TextBox)和裝飾層(AdornerLayer)本來(lái)就同屬于AdornerDecorator,所以在AdornerDecorator的Measure方法中會(huì)調(diào)用裝飾控件和裝飾層的Measure方法,裝飾層又會(huì)用同樣的方法刷新它的子類(lèi)也就是我們的SimpleTextBlockAdorner,子調(diào)用父,父又調(diào)用子,子接著調(diào)用父?不是死循環(huán)了么?所以這里WPF用了變量MeasureInProgress和MeasureDirty來(lái)控制,如果已經(jīng)在Measure中,則不需要循環(huán)調(diào)用。
?
這樣下來(lái)你是不是感覺(jué)布局系統(tǒng)是很耗費(fèi)資源的呢?^ 0 ^
?
?
另外對(duì)于A(yíng)dorner中的GetDesiredTransform方法,其實(shí)看過(guò)AdornerLayer中的布局方法ArrangeOverride就可窺其詳了
protected override Size ArrangeOverride(Size finalSize) {DictionaryEntry[] array = new DictionaryEntry[this._zOrderMap.Count];this._zOrderMap.CopyTo(array, 0);for (int i = 0; i < array.Length; i++){ArrayList list = (ArrayList)array[i].Value;int num2 = 0;while (num2 < list.Count){AdornerInfo info = (AdornerInfo)list[num2++];if (!info.Adorner.IsArrangeValid){Point location = new Point();info.Adorner.Arrange(new Rect(location, info.Adorner.DesiredSize)); GeneralTransform desiredTransform = info.Adorner.GetDesiredTransform(info.Transform);GeneralTransform proposedTransform = this.GetProposedTransform(info.Adorner, desiredTransform);int index = this._children.IndexOf(info.Adorner);if (index >= 0){Transform transform3 = (proposedTransform != null) ? proposedTransform.AffineTransform : null;((Adorner)this._children[index]).AdornerTransform = transform3;}}if (info.Adorner.IsClipEnabled){info.Adorner.AdornerClip = info.Clip;}else if (info.Adorner.AdornerClip != null){info.Adorner.AdornerClip = null;}}}return finalSize; }?
GeneralTransform?desiredTransform = info.Adorner.GetDesiredTransform(info.Transform);
((Adorner)this._children[index]).AdornerTransform = transform3;
從中我們可以看出,分配的時(shí)候就是把從GetDesiredTransform得到的值又返回給Adorner的AdornerTransform屬性,而AdornerTransform屬性其實(shí)
RenderTransform屬性我們總熟悉了吧,不熟悉?那看這個(gè)吧http://msdn.microsoft.com/zh-cn/library/system.windows.uielement.rendertransform.aspx
用RenderTransform可以比較肯定的說(shuō),速度要比普通布局快,因?yàn)樗窃诓季种笈?#xff0c;并不牽涉到反復(fù)的可視樹(shù)傳遞引發(fā),所以動(dòng)畫(huà)盡量以改變此值為主。
?
我另外標(biāo)示的GeneralTransform?proposedTransform =?this.GetProposedTransform(info.Adorner, desiredTransform);?也一定好奇吧,做什么呢?其實(shí)就是開(kāi)始說(shuō)的FlowDirection問(wèn)題,反轉(zhuǎn)上面的控件用的。
private GeneralTransform GetProposedTransform(Adorner adorner, GeneralTransform sourceTransform) {if (adorner.FlowDirection == base.FlowDirection){return sourceTransform;}GeneralTransformGroup group = new GeneralTransformGroup();Matrix matrix = new Matrix(-1.0, 0.0, 0.0, 1.0, adorner.RenderSize.Width, 0.0);MatrixTransform transform = new MatrixTransform(matrix);group.Children.Add(transform);if ((sourceTransform != null) && (sourceTransform != Transform.Identity)){group.Children.Add(sourceTransform);}return group; }?
三.默認(rèn)控件的應(yīng)用
?
???????
????????GridSplitter???Grid上的拖拉控件,我想大家應(yīng)該不用吃驚吧,它是寫(xiě)了個(gè)PreviewAdorner來(lái)移動(dòng)。在網(wǎng)上看到了這個(gè)鏈接http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/dfff9b89-81a8-4bfc-852d-d08ccdffe6bb?提問(wèn)者改變了Window的模板,并在模板中放了Grid和GridSplitter,為什么會(huì)報(bào)錯(cuò)?現(xiàn)在我們知道了,Window默認(rèn)的模板中有ScrollViewer,可以產(chǎn)生AdornerLayer,而改變的模板中沒(méi)有AdornerLayer的容器,而且Window已經(jīng)是窗口的最高層控件,沿可視樹(shù)向上找也不會(huì)有其他的控件,所以GridSplitter不可能獲取到AdornerLayer,因此就拋了NullReferenceException。解決辦法便是在GridSplitter??外面套一層有AdornerLayer的東西,或ScrollViewer或AdornerDecorator,在鏈接中回答者給出的是AdornerDecorator。
?
?????? Validation?驗(yàn)證時(shí)候用的模板,其實(shí)你看到的這些感嘆號(hào),外框都是在層上的,他和GridSplitter?不同的是他可以在外面定義個(gè)模板,可以讓用戶(hù)自己指定要呈現(xiàn)的東西,為此他它寫(xiě)了個(gè)TemplatedAdorner,為什么找不到Validation?的默認(rèn)模板,因?yàn)樗么a寫(xiě)死了。當(dāng)然如果你發(fā)現(xiàn)驚嘆號(hào),外框不在該有地方,也容易做了——肯定層的位置有問(wèn)題嘛。
private static ControlTemplate CreateDefaultErrorTemplate() {ControlTemplate template = new ControlTemplate(typeof(Control));FrameworkElementFactory factory = new FrameworkElementFactory(typeof(Border), "Border");factory.SetValue(Border.BorderBrushProperty, Brushes.Red);factory.SetValue(Border.BorderThicknessProperty, new Thickness(1.0));FrameworkElementFactory child = new FrameworkElementFactory(typeof(AdornedElementPlaceholder), "Placeholder");factory.AppendChild(child);template.VisualTree = factory;template.Seal();return template; }至于AdornedElementPlaceholder這個(gè)占位符,它的大小是驗(yàn)證控件(TextBox)的大小,可他卻是在模板中定義的,那么他如何來(lái)知道具體的驗(yàn)證控件是什么呢,這里它經(jīng)過(guò)TemplatedAdorner中的AdornedElement來(lái)達(dá)到效果。可以說(shuō)的上奇巧淫技,它使得AdornedElementPlaceholder知道具體的TemplatedAdorner,可TemplatedAdorner并不知曉具體的AdornedElementPlaceholder,但在AdornedElementPlaceholder同時(shí)觀(guān)察到
所以他的有效作用只有一個(gè)。好的控件是能夠更好的解耦,可解耦的前提是原來(lái)的控件有一定的預(yù)留,TemplatedAdorner便是預(yù)留了ReferenceElement來(lái)達(dá)到效果。
?
四.自定義個(gè)遮罩控件
?
說(shuō)了這么多是不是技癢了,那先來(lái)做個(gè)簡(jiǎn)單的吧,有時(shí)當(dāng)我們讀取數(shù)據(jù)希望未顯示完的列表不需要讓客戶(hù)操作,所以需要要這個(gè)遮罩層來(lái)檔下,一方面為了不讓客戶(hù)操作具體控件,令一方面可以讓客戶(hù)看到事情進(jìn)度或操作信息。那怎么做比較舒服呢?自然的,我希望遮罩只針對(duì)某個(gè)控件而已,因?yàn)槠渌胤讲⒉挥绊?#xff0c;依然可以操作。在Demo上就簡(jiǎn)化了。顯示信息的模板可以自定義修改,有沒(méi)有感覺(jué)和剛才說(shuō)的TemplatedAdorner模板有類(lèi)似。所以發(fā)揮拿來(lái)主義的精神。
代碼就不在這里列舉了,不過(guò)要注意的是要把上面的模板控件加入可視樹(shù),要不然會(huì)穿越,就達(dá)不到阻擋的作用,同理如若需要穿越操作的話(huà),可以不加入可視樹(shù)。
對(duì)于代碼有些人喜歡完全的附加屬性如Validation?那樣的賦值,我個(gè)人比較喜歡用類(lèi)賦值,如果不喜歡可以動(dòng)動(dòng)手自己改掉,調(diào)用代碼如下:
<ListView ItemsSource="{Binding Employees}"><ControlLibrary:MaskAttach.MaskAttach><ControlLibrary:MaskAttach x:Name="fff" DataContext="{Binding Progress}" Open="{Binding IsLoading}"><ControlLibrary:MaskAttach.Template><DataTemplate><Grid><Grid.ColumnDefinitions><ColumnDefinition /><ColumnDefinition Width="40"/></Grid.ColumnDefinitions><Rectangle Grid.ColumnSpan="2" Fill="Black" Opacity="0.7"/><TextBlock Grid.Column="1" Margin="5" Foreground="White" HorizontalAlignment="Right" VerticalAlignment="Center"><AccessText Text="{Binding}"/><AccessText>%</AccessText></TextBlock><ProgressBar Margin="10" Value="{Binding Mode=OneWay}" Height="20"/></Grid></DataTemplate></ControlLibrary:MaskAttach.Template></ControlLibrary:MaskAttach></ControlLibrary:MaskAttach.MaskAttach>在這里還要說(shuō)明的是,AdornerLayer.GetAdornerLayer取得層的時(shí)候最后是放在d.Dispatcher.BeginInvoke中取,因?yàn)橛袝r(shí)候需要等上面加載完,對(duì)于DispatcherPriority我一般選的是Render。
而DependencyProperty的屬性值改變偵測(cè),如果用Binding的話(huà)則需要對(duì)象一定要從FrameworkElement派生的,但可以利用DependencyPropertyDescriptor偵測(cè):
var dpdDataContext = DependencyPropertyDescriptor.FromProperty(MaskAttach.DataContextProperty, maskAttach.GetType()); dpdDataContext.AddValueChanged(maskAttach, delegate{d.SetValue(MaskAttach.DataContextProperty, maskAttach.DataContext);});?
?
五.Decorator
?
?? 當(dāng)看到Decorator讓人更容易的想到Decorator模式,Decorator在我的印象中更接近一個(gè)包裝器,把原有的方法放入包裝類(lèi)的一個(gè)同名方法中,在同名方法中再加些其他的功能罷了。
??? 如果說(shuō)里面顏色塊代表功能大小的化,很明顯包裝類(lèi)的功能更強(qiáng)大。而且他增加功能的話(huà)又對(duì)原來(lái)的A類(lèi)結(jié)構(gòu)是無(wú)損的,用戶(hù)通過(guò)接口來(lái)操作的話(huà)也無(wú)須知道實(shí)體類(lèi)。
??? 就WPF而言,用戶(hù)所要的是呈現(xiàn)效果,在外包一層改變了顯示效果但是不影響原有控件的效果和功能。所以他可以操作控件的外觀(guān)常用的Border控件,以及改變控件現(xiàn)實(shí)大小的ViewBox都是繼承于Decorator類(lèi),還有就是我們之上提到的AdornerDecorator。都是對(duì)原來(lái)控件外觀(guān)或控制的擴(kuò)展。
Decorator本身只是個(gè)單容器控件,只能對(duì)一個(gè)控件進(jìn)行裝飾,Panel的話(huà)是多容器,可以對(duì)多控件進(jìn)行裝飾,控件大小的位置的改變也是裝飾的一種表現(xiàn)形式。本想做個(gè)簡(jiǎn)單的拖拽控件,網(wǎng)上搜索了下發(fā)現(xiàn)已經(jīng)有人做了。http://codeblitz.wordpress.com/2009/06/10/wpf-dragdrop-decorator-for-itemscontrol/
網(wǎng)頁(yè)似乎被墻,我看的是快照,示例程序我也放上來(lái)了以免以后下不到。
對(duì)于默認(rèn)拖拽的顯示和做法,下面也記錄下:
1.拖拽一般是兩個(gè)控件之間的數(shù)據(jù)交互,就是拖拉的時(shí)候(MouseDown)把數(shù)據(jù)放到一個(gè)地方,拖拉完之后(MouseUp)再把這個(gè)數(shù)據(jù)放到另一個(gè)控件中,所以拖拽的兩個(gè)控件本身要提供拖拽,繼承于UIElement的控件可以直接把AllowDrop屬性設(shè)置為T(mén)rue.
2.在鼠標(biāo)點(diǎn)擊也就是MouseDown,一般注冊(cè)MouseDown事件或直接重載OnMouseDown方法,把選中的數(shù)據(jù)放到變量中,你選中的是控件?控件是數(shù)據(jù)的呈現(xiàn),所以你應(yīng)該能拿的到數(shù)據(jù)。除非那個(gè)控件真的沒(méi)有數(shù)據(jù),那也就不需要拉了,有數(shù)據(jù)的話(huà),我們把數(shù)據(jù)放到一個(gè)變量_mouseDownData中。
3.在鼠標(biāo)移動(dòng)的過(guò)程中,我們看到鼠標(biāo)的樣式是會(huì)動(dòng)的(鼠標(biāo)下面會(huì)有小方塊),而且我們傳數(shù)據(jù)也需要個(gè)方法傳對(duì)吧,所在鼠標(biāo)移動(dòng)的時(shí)候有MouseMove做下面這句
DragDrop.DoDragDrop((ItemsControl)sender, _mouseDownData, DragDropEffects.Move | DragDropEffects.Copy);4.鼠標(biāo)放開(kāi)就就是MouseUp的話(huà),把傳遞的數(shù)據(jù)的變量mouseDownData清空或賦值為Null.
5.拖拉有兩方,假設(shè)要把A數(shù)據(jù)拖到B上,那么A調(diào)用了DragDrop.DoDragDrop方法去放,B怎么接收的呢?B控件要注冊(cè)PreviewDrop事件,通過(guò)DragEventArgs e參數(shù)來(lái)獲得e.Data,其中數(shù)據(jù)類(lèi)型一般先e.Data.GetDataPresent(typeof(數(shù)據(jù)類(lèi)型))來(lái)判斷有沒(méi)有,然后通過(guò)e.Data.GetData(typeof(數(shù)據(jù)類(lèi)型))具體拿值,e.Effects可以用來(lái)判斷操作:是否要把數(shù)據(jù)添加進(jìn)B控件。
6.假定拖拉到一般要取消怎么辦?控件注冊(cè)PreviewQueryContinueDrag事件在QueryContinueDragEventArgs e中對(duì)e.Action進(jìn)行賦值操作,可以DragAction.Cancel當(dāng)然也可以DragAction.Drop或者DragAction.Continue了。
?
關(guān)于拖拽這里還有個(gè)文章:http://www.cnblogs.com/taowen/archive/2008/10/30/1323329.html
?
六.純粹的個(gè)人感概
??????
????? 在我學(xué)習(xí)WPF的第一個(gè)月,可以說(shuō)自我感覺(jué)最良好的時(shí)候,當(dāng)時(shí)認(rèn)為什么都可以做了,WPF不過(guò)偶爾,憑借著以前的Winform開(kāi)發(fā)思路,在OnRender中大放光彩,認(rèn)為什么都能做,所以之前的控件都是自己重做的,看微軟默認(rèn)的不爽就重寫(xiě),沒(méi)有的就自己造,后來(lái)慢慢的,開(kāi)始MVVM,開(kāi)始大量的轉(zhuǎn)變控件,雖然默認(rèn)的控件大部分到最后也都是Draw出來(lái)的,但是使用默認(rèn)控件拼出新控件卻是團(tuán)隊(duì)溝通的橋梁,默認(rèn)控件一般都能滿(mǎn)足需求,自己定義的話(huà)可能效率有一定優(yōu)勢(shì),開(kāi)始的時(shí)候也方便,到最后做大做復(fù)雜也挺麻煩,最主要的是團(tuán)隊(duì)成員的樣式套用就麻煩,整體效果就有影響。之前自己有個(gè)想法,就是控件加快開(kāi)發(fā)進(jìn)度的,所以不好用的就不用了,實(shí)際上是也沒(méi)怎么認(rèn)真去想怎么用。默認(rèn)控件的模式和思路都是值得研究和學(xué)習(xí)的。當(dāng)然如果你的控件需要一定的運(yùn)行效率那就只能重做一份了。
????? 關(guān)于模式,面向?qū)ο箝_(kāi)發(fā),都是希望把責(zé)任分的更清晰,把功能切的更細(xì),那把責(zé)任和功能切的更細(xì)的意義是什么呢,易于維護(hù),可團(tuán)隊(duì)里的每個(gè)人的思維形態(tài)并不一樣,你認(rèn)為這樣好,可人家卻很難理解,難理解之后溝通是不是也難了,開(kāi)發(fā)效率怎么上的去?所以利于溝通的設(shè)計(jì)才是好的設(shè)計(jì)。當(dāng)團(tuán)隊(duì)思想慢慢的趨于一致,再發(fā)揮你的才干,你可能會(huì)得到更好的幫助和建議。有些事不必要急于一時(shí)。
????? 在這里感謝很多熱心人的幫助,特別是周永恒,高手雖多可忙的不少,樂(lè)于解答的更少,幫你分析的少之又少。而周永恒卻可以幫你具體講解分析,在這里表示由衷的謝意。
轉(zhuǎn)載于:https://www.cnblogs.com/jojinshallar/articles/3372323.html
總結(jié)
以上是生活随笔為你收集整理的WPF自定义控件 —— 装饰器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 微信小程序点击图片全屏展示,并可以翻下一
- 下一篇: Csharp+Asp.net系列教程(四