C# 中那些常用的工具类(Utility Class)(二)
今天按照这一年来经常用到的那些静态的工具类再来做一次总结,这些小的工具来可以作为自己学习的很好的例子,通过总结这些东西,能够很大程度上梳理自己的知识体系,当然这个是经常用到的,接下来就一个个去分析这些类的作用和蕴含的知识点。
ControlUtil
这个工具类是非常重要的,它的主要作用就是通过视觉树和逻辑树去寻找父节点或者子节点,这个例子在网上非常多,其核心都是通过迭代的思想去寻找自己最终想要的子控件,当然这里面有很多的内容,下面通过一个个的实例来说明这些代码的具体作用。
/// <summary>
/// 查找制定类别的子控件
/// </summary>
/// <typeparam name="T">控件类别</typeparam>
/// <param name="parent">父控件</param>
/// <returns>子控件集合</returns>
public static List<T> FindChildControls<T>(UIElement parent)
where T : UIElement
{
List<T> list = new List<T>();
if (parent == null) return list; if (parent is ContentControl)
{
if ((parent as ContentControl).Content is T)
{
list.Add((parent as ContentControl).Content as T);
}
list.AddRange(FindChildControls<T>((parent as ContentControl).Content as UIElement));
} if (parent is Decorator)
{
if ((parent as Decorator).Child is T)
list.Add((parent as Decorator).Child as T);
list.AddRange(FindChildControls<T>((parent as Decorator).Child));
} if (parent is ItemsControl)
{
ItemsControl ic = parent as ItemsControl;
foreach (object itm in ic.Items)
{
if (itm is T)
list.Add(itm as T);
if (itm is UIElement)
list.AddRange(FindChildControls<T>(itm as UIElement));
}
} if (parent is Panel)
{
Panel pan = parent as Panel;
foreach (UIElement ui in pan.Children)
{
if (ui is T)
{
list.Add(ui as T);
}
list.AddRange(FindChildControls<T>(ui));
}
} return list;
}
由于传入的参数是UIElement,非常顶层的元素,所以在代码中我们进行了不同的分类,具体的分类分为下面几种ContentControl、Decorator、ItemsControl、Panel,通过这个分类基本上可以把父元素的所有子控件全部枚举出来,当然这段代码不太理想的地方就是过于庞大,无法准确去获取某一个元素,如果传入顶层的元素,那么我们将获取非常庞大的内容,这个具体的可以写一些测试代码去测试!
public static T FindVisualParent<T>(DependencyObject reference) where T : class
{
var parent = VisualTreeHelper.GetParent(reference);
if (parent == null)
return null;
else
{
if (parent is T)
{
return parent as T;
}
else
{
return FindVisualParent<T>(parent);
}
}
}
这段代码非常好理解,就是通过VisualTreeHelper来不断去获取Parent直到获取到第一个类型为T的父类型,下面我会针对这几种类型来写一个测试的demo,现在我来说说这些主要用在哪些地方。
a 比如重写了Button的模板,但是在点击的时候首先获取点击事件的可能是Button里面能够接受焦点的元素,这个时候我们可以传入首先获取焦点的对象,通过这个方法来获取Button,比如由于WPF支持事件的路由,所以在这种情况下通过传入e.OriginalSource来获取到Button,然后为Button做各种操作。
b 为当前对象this查找父类容器,比如我们我们经常会用到将Model绑定到前台的控件中(比如ItemsControl)那么我们可以传入当前的Model所应用到的数据模板中的对象(例如Image、TextBox、Label......),然后获取到父容器或者其他的对象,这种也是非常常用的方面(但是这里需要特别注意的地方是传入的参数必须是继承自DependencyObject的对象)。
public static List<T> FindVisualChildren<T>(DependencyObject reference)
where T : UIElement
{
List<T> list = new List<T>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(reference); i++)
{
var child = VisualTreeHelper.GetChild(reference, i);
if (child != null && child is T)
{
list.Add(child as T);
}
list.AddRange(FindVisualChildren<T>(child));
}
return list;
}
与寻找父类型不同,这段代码能够获取到所有的类型为T的子元素,并返回一个List<T>的对象,这个方法的好处就是能够沿着当前传入的DependencyObject为起点沿着视觉树一直向下查找,包括模板中找到的元素。
下面通过一个DEMO(一个简单的照片浏览器)来说明一下,首先看一下软件界面。
这是一个简单的照片浏览程序,右边是一个图像列表,是一个ListBox对象,里面绑定了一个ObservableCollection的集合,当点击每一个ListBoxItem对象时,左侧会弹出相应的对象,同时下面的按钮也能够同步控制图片的选择功能,对于这个DEMO我们来一步步去分析视觉树上的父对象和子对象!
首先来看看MainWindow.xaml(这里仅仅贴出核心的代码)
<Grid Background="Gray" x:Name="root">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
<RowDefinition Height="80"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="150"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="照片浏览器" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Foreground="White" FontSize="28" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="楷体"/>
<!--<ContentPresenter Grid.Row="1" Content="{Binding Path=ImgCollection}"/>--> <ListBox x:Name="pictureItemsControl" Grid.Row="1" Grid.Column="1" ItemsSource="{Binding ImageCollectionView,Mode=TwoWay}" AlternationCount="9999" ClipToBounds="False" FocusVisualStyle="{x:Null}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<interactive:ExInvokeCommandAction Command="{Binding OnListBoxSelected}">
</interactive:ExInvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.Template>
<ControlTemplate TargetType="ItemsControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="Teal" CornerRadius="2">
<Label Content="照片列表" HorizontalAlignment="Center" VerticalAlignment="Center"></Label>
</Border>
<Border Padding="2" Background="Teal" CornerRadius="2" Grid.Row="1">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsPresenter></ItemsPresenter>
</ScrollViewer>
</Border>
</Grid>
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:PictureModel}">
<Border Padding="2">
<Image Source="{Binding ImgSource, Mode=TwoWay}" Stretch="Fill" Width="120" Height="120" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Image Grid.Row="1" Grid.Column="0" Source="{Binding CurrentItem.ImgSource, Mode=TwoWay}" Stretch="Uniform" Width="Auto" Height="Auto"/>
<Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Command="{Binding FirstPicture, Mode=TwoWay}" Width="60" Height="60" Margin="10,0,0,0" RenderTransformOrigin="0.5,0.5">
<Button.Background>
<ImageBrush ImageSource="/CollectionView;Component/Images/First.png"/>
</Button.Background>
</Button>
<Button Command="{Binding PreviousPicture, Mode=TwoWay}" Width="60" Height="60" Margin="10,0,0,0" RenderTransformOrigin="0.5,0.5">
<Button.Background>
<ImageBrush ImageSource="/CollectionView;Component/Images/Forward.png"/>
</Button.Background>
</Button>
<Button Command="{Binding NextPicture, Mode=TwoWay}" Width="60" Height="60" Margin="10,0,0,0" RenderTransformOrigin="0.5,0.5">
<Button.Background>
<ImageBrush ImageSource="/CollectionView;Component/Images/Next.png"/>
</Button.Background>
</Button>
<Button Command="{Binding LastPicture, Mode=TwoWay}" Width="60" Height="60" Margin="10,0,0,0" RenderTransformOrigin="0.5,0.5">
<Button.Background>
<ImageBrush ImageSource="/CollectionView;Component/Images/Last.png"/>
</Button.Background>
</Button>
</StackPanel>
</Grid>
</Grid>
</Grid>
在ViewModel层绑定到前台ListBox的总共有7个对象(七个图片),定义了一个CollectionView的集合。
private System.Windows.Data.CollectionView _imageCollectionView
= null; public System.Windows.Data.CollectionView ImageCollectionView
{
get
{
return _imageCollectionView;
}
set
{
if (value != _imageCollectionView)
{
_imageCollectionView = value;
RaisePropertyChanged<System.Windows.Data.CollectionView>(() => this.ImageCollectionView);
}
}
}
在后台的代码中,我们通过 List<Image> myImageList = X.Utils.ControlUtil.FindVisualChildren<Image>(lb) 其中lb是通过程序中获取到的ListBox对象,通过代码调用我们能够获取到所有的ListBox中的Image对象(注:这个对象是在ListBox.ItemTemplate中定义的),总共获取到了7个对象,没有任何问题。
另外我们再进行一个测试,在这段代码中List<Image> myImageList = X.Utils.ControlUtil.FindVisualChildren<Image>(this.root),即向代码中传入根节点Grid,通过代码总共能够获取到8个对象,即ListBox中的7个对象另外再加上左侧的一张大的图片,总共8张。<Image Grid.Row="1" Grid.Column="0" Source="{Binding CurrentItem.ImgSource, Mode=TwoWay}" Stretch="Uniform" Width="Auto" Height="Auto"/>(大图片)
同样的道理我们可以去获取当前对象的VisualParent,同样也是通过这种方式来获取的......
这里我需要重点介绍的是我们都知道FrameworkElement下面有一个Parent属性,返回的是DependencyObject属性,MSDN的解释是:获取此元素的逻辑父元素。那么这个属性和通过VisualTreeHelper有什么不同呢?我们也列出了一些常用的代码,并通过DEMO来说明这些用法。
public static Window FindParentWindow(FrameworkElement element)
{
if (element.Parent == null)
{
return null;
} Window window = element.Parent as Window;
if (window != null)
{
return window;
} FrameworkElement parentElement = element.Parent as FrameworkElement;
if (parentElement != null)
{
return FindParentWindow(parentElement);
} return null;
}
通过代码我们可以发现,这个方法只能够获取到当前节点的逻辑父节点,而且只能够一级一级的去迭代获取,如果其中一个元素的Parent为null的话那么就不能够得到最终的父元素。还是前面的那个例子,我们通过VS 的WPF Tree Visualizer这个工具可以很清楚的看到整个视觉树的所有元素,我们发现视觉树中的很多元素其Parent都是null,特别是定义在模板中的元素,Parent属性都是null,这些该怎么解释呢?FrameWorkElement.Parent
是获取此元素的逻辑父级元素,也就是通过LogicalTreeHelper查找。在MSDN中备注了:
对于模板,模板的 Parent 最终将为 null。 若要忽略这一点并扩展到实际应用模板的逻辑树,请使用 TemplatedParent,也就是通过这种方式查找父元素时只能在逻辑树上查找,否则只能返回为Null。
按照这个思路我们再一步步分析,请看下面的代码:
public static FrameworkElement FindParentWithDataContextAndName<DataContextT>(FrameworkElement childElement, string name)
where DataContextT : class
{
FrameworkElement parent = (FrameworkElement)childElement.Parent;
if (parent != null)
{
DataContextT data = parent.DataContext as DataContextT;
if (data != null)
{
if (parent.Name == name)
{
return parent;
}
} parent = FindParentWithDataContextAndName<DataContextT>(parent, name);
if (parent != null)
{
return parent;
}
} parent = (FrameworkElement)childElement.TemplatedParent;
if (parent != null)
{
DataContextT data = parent.DataContext as DataContextT;
if (data != null)
{
if (parent.Name == name)
{
return parent;
}
} parent = FindParentWithDataContextAndName<DataContextT>(parent, name);
if (parent != null)
{
return parent;
}
} return null;
}
这段代码是寻找特定的DataContext和name的Parent对象,在代码中如果通过FrameWorkElement.Parent来获取到的对象为null的话,就会通过parent = (FrameworkElement)childElement.TemplatedParent;通过当前元素的TemplateParent来获取父元素,通过这种方式是可以找到特定的DataContext和name的Parent对象,这点我们也可以通过实际的代码去验证的。
下面的这段代码和上面的类似,这里不再赘述!
public static FrameworkElement FindParentWithDataContext<DataContextT>(FrameworkElement childElement)
where DataContextT : class
{
if (childElement.Parent != null)
{
DataContextT data = ((FrameworkElement)childElement.Parent).DataContext as DataContextT;
if (data != null)
{
return (FrameworkElement)childElement.Parent;
} FrameworkElement parent = FindParentWithDataContext<DataContextT>((FrameworkElement)childElement.Parent);
if (parent != null)
{
return parent;
}
} if (childElement.TemplatedParent != null)
{
DataContextT data = ((FrameworkElement)childElement.TemplatedParent).DataContext as DataContextT;
if (data != null)
{
return (FrameworkElement)childElement.TemplatedParent;
} FrameworkElement parent = FindParentWithDataContext<DataContextT>((FrameworkElement)childElement.TemplatedParent);
if (parent != null)
{
return parent;
}
} return null;
}
下面来看看将两种方式组合来查找特定类型的父对象以及子对象
public static ParentT FindParentWithType<ParentT>(FrameworkElement childElement)
where ParentT : class
{
if (childElement.Parent != null)
{
ParentT parent = childElement.Parent as ParentT;
if (parent != null)
{
return parent;
} parent = FindParentWithType<ParentT>((FrameworkElement)childElement.Parent);
if (parent != null)
{
return parent;
}
} if (childElement.TemplatedParent != null)
{
ParentT parent = childElement.TemplatedParent as ParentT;
if (parent != null)
{
return parent;
} parent = FindParentWithType<ParentT>((FrameworkElement)childElement.TemplatedParent);
if (parent != null)
{
return parent;
}
} FrameworkElement parentElement = (FrameworkElement)VisualTreeHelper.GetParent(childElement);
if (parentElement != null)
{
ParentT parent = parentElement as ParentT;
if (parent != null)
{
return parent;
} return FindParentWithType<ParentT>(parentElement);
} return null;
}
查找子对象
/// <summary>
/// Find the framework element for the specified connector.
/// </summary>
public static ElementT FindElementWithType<ElementT>(Visual rootElement)
where ElementT : FrameworkElement
{
if (rootElement == null)
{
throw new ArgumentNullException("rootElement");
} FrameworkElement rootFrameworkElement = rootElement as FrameworkElement;
if (rootFrameworkElement != null)
{
rootFrameworkElement.UpdateLayout();
} //
// Check each child.
//
int numChildren = VisualTreeHelper.GetChildrenCount(rootElement);
for (int i = 0; i < numChildren; ++i)
{
Visual childElement = (Visual)VisualTreeHelper.GetChild(rootElement, i); ElementT typedChildElement = childElement as ElementT;
if (typedChildElement != null)
{
return typedChildElement;
}
} //
// Check sub-trees.
//
for (int i = 0; i < numChildren; ++i)
{
Visual childElement = (Visual)VisualTreeHelper.GetChild(rootElement, i); ElementT foundElement = FindElementWithType<ElementT>(childElement);
if (foundElement != null)
{
return foundElement;
}
} return null;
}
再介绍一个重载的方法用于查找特定类型,特定DataContext的父元素以及子元素,这个方法在WPF这种MVVM模式下是非常重要的。
public static ParentT FindParentWithTypeAndDataContext<ParentT>(FrameworkElement childElement, object dataContext)
where ParentT : FrameworkElement
{
if (childElement.Parent != null)
{
ParentT parent = childElement.Parent as ParentT;
if (parent != null)
{
if (parent.DataContext == dataContext)
{
return parent;
}
} parent = FindParentWithTypeAndDataContext<ParentT>((FrameworkElement)childElement.Parent, dataContext);
if (parent != null)
{
return parent;
}
} if (childElement.TemplatedParent != null)
{
ParentT parent = childElement.TemplatedParent as ParentT;
if (parent != null)
{
if (parent.DataContext == dataContext)
{
return parent;
}
} parent = FindParentWithTypeAndDataContext<ParentT>((FrameworkElement)childElement.TemplatedParent, dataContext);
if (parent != null)
{
return parent;
}
} FrameworkElement parentElement = (FrameworkElement)VisualTreeHelper.GetParent(childElement);
if (parentElement != null)
{
ParentT parent = parentElement as ParentT;
if (parent != null)
{
return parent;
} return FindParentWithType<ParentT>(parentElement);
} return null;
}
查找子元素
public static ElementT FindElementWithDataContext<DataContextT, ElementT>(Visual rootElement, DataContextT data)
where DataContextT : class
where ElementT : FrameworkElement
{
if (rootElement == null)
{
throw new ArgumentNullException("rootElement");
} FrameworkElement rootFrameworkElement = rootElement as FrameworkElement;
if (rootFrameworkElement != null)
{
rootFrameworkElement.UpdateLayout();
} int numChildren = VisualTreeHelper.GetChildrenCount(rootElement);
for (int i = 0; i < numChildren; ++i)
{
Visual childElement = (Visual)VisualTreeHelper.GetChild(rootElement, i); ElementT typedChildElement = childElement as ElementT;
if (typedChildElement != null &&
typedChildElement.DataContext == data)
{
return typedChildElement;
} ElementT foundElement = FindElementWithDataContext<DataContextT, ElementT>(childElement, data);
if (foundElement != null)
{
return foundElement;
}
} return null;
}
最后再以一个非常常见的查找DataTemplate的代码来结束这一节的内容。
在实际的项目开发中查找某一个元素使用了何种DataTemplate也是有需要的,通过为某一个对象定义DataTemplate,同时定义DataTemplate的Key我们就能够获取到相应的元素,通过下面的代码我们也能够获取到相应的内容,仅供参考。
public static DataTemplateT FindTemplateForType<DataTemplateT>(Type type, FrameworkElement element)
where DataTemplateT : class
{
object resource = element.TryFindResource(new DataTemplateKey(type));
DataTemplateT dataTemplate = resource as DataTemplateT;
if (dataTemplate != null)
{
return dataTemplate;
} if (type.BaseType != null &&
type.BaseType != typeof(object))
{
dataTemplate = FindTemplateForType<DataTemplateT>(type.BaseType, element);
if (dataTemplate != null)
{
return dataTemplate;
}
} foreach (Type interfaceType in type.GetInterfaces())
{
dataTemplate = FindTemplateForType<DataTemplateT>(interfaceType, element);
if (dataTemplate != null)
{
return dataTemplate;
}
} return null;
}
C# 中那些常用的工具类(Utility Class)(二)的更多相关文章
- C#中那些常用的工具类(Utility Class)(一)
代码越写越多,但是我们也需要经常去反思那些写过的代码,Utility Class就是这一类需要特别去反思总结的类,这些类像工具一样,我们经常通过一些静态方法,通过传入一些参数,然后得到我们需要的结果, ...
- C# 中那些常用的工具类(Utility Class)(三)
今天来接着写这个系列的文章,这一篇主要是用来介绍关于C#中的XML序列化的问题,这个相信大家一定会经常使用它,特别是在WPF中,有时候我们需要将我们后台的数据保存在数据库中,从而在软件下一次启动的时候 ...
- commons-collections包中的常用的工具类
commons-collections包中的常用的工具类 <dependency> <groupId>commons-collections</groupId> & ...
- Hutool中那些常用的工具类和方法
Hutool中那些常用的工具类和方法 Hutool是一个Java工具包,它帮助我们简化每一行代码,避免重复造轮子.如果你有需要用到某些工具方法的时候,不妨在Hutool里面找找,可能就有.本文将对Hu ...
- Android常用的工具类
主要介绍总结的Android开发中常用的工具类,大部分同样适用于Java.目前包括HttpUtils.DownloadManagerPro.ShellUtils.PackageUtils. Prefe ...
- Android常用的工具类(转)
主要介绍总结的Android开发中常用的工具类,大部分同样适用于Java.目前包括HttpUtils.DownloadManagerPro.ShellUtils.PackageUtils.Prefer ...
- 2013最新Android常用的工具类整理
主要介绍总结的Android开发中常用的工具类,大部分同样适用于Java. 目前包括HttpUtils.DownloadManagerPro.ShellUtils.PackageUtils. Pref ...
- Java语言Lang包下常用的工具类介绍_java - JAVA
文章来源:嗨学网 敏而好学论坛www.piaodoo.com 欢迎大家相互学习 无论你在开发哪中 Java 应用程序,都免不了要写很多工具类/工具函数.你可知道,有很多现成的工具类可用,并且代码质量都 ...
- Java,面试题,简历,Linux,大数据,常用开发工具类,API文档,电子书,各种思维导图资源,百度网盘资源,BBS论坛系统 ERP管理系统 OA办公自动化管理系统 车辆管理系统 各种后台管理系统
Java,面试题,简历,Linux,大数据,常用开发工具类,API文档,电子书,各种思维导图资源,百度网盘资源BBS论坛系统 ERP管理系统 OA办公自动化管理系统 车辆管理系统 家庭理财系统 各种后 ...
随机推荐
- UVA11059-Maximum Product(动态规划)
Problem UVA11059-Maximum Product Accept:4769 Submit:38713 Time Limit: 3000 mSec Problem Descriptio ...
- 如何利用pip自动生成和安装requirements.txt依赖
在查看别人的Python项目时,经常会看到一个requirements.txt文件,里面记录了当前程序的所有依赖包及其精确版本号.这个文件有点类似与Rails的Gemfile.其作用是用来在另一台PC ...
- 深入学习vue指令,自定义指令解决开发痛点
每天学习一点点 编程PDF电子书.视频教程免费下载:http://www.shitanlife.com/code v-model指令 vue.js的定义是一个mvvm框架,将它发挥到极致能够极大的提升 ...
- 【转】使用ffmpeg转码的MP4文件需要加载完了才能播放的解决办法
1.前一段时间做了一个ffmpeg转码MP4的项目,但是转出来的MP4部署在网站上需要把整个视频加载完成才能播放,到处找资料,最后找到解决方案记录于此备忘. FFMpeg转码由此得到的mp4文件中, ...
- 七彩爱心灯手机APP
安卓IDE3.20以后不包含sdk,需要更新重新下载. 1 下载工程 https://github.com/Dongvdong/Lovelamp_app 2打开工程 如果换了工程移动换了文件夹 (1) ...
- Spring Security(二十二):6.4 Method Security
From version 2.0 onwards Spring Security has improved support substantially for adding security to y ...
- 从Linux内核角度看中间人攻击(ARP欺骗)并利用Python scapy实现
邻居子系统与ARP协议 邻居子系统的作用就是将IP地址,转换为MAC地址,类似操作系统中的MMU(内存管理单元),将虚拟地址,转换为物理地址. 其中邻居子系统相当于地址解析协议(IPv4的ARP协议, ...
- 环境部署(四):Linux下查看JDK安装路径
在安装好Git.JDK和jenkins之后,就需要在jenkins中进行对应的设置,比如在全局工具配置模块,需要写入JDK的安装路径. 这篇博客,介绍几种常见的在Linux中查看JDK路径的方法... ...
- 4-STM32物联网开发WIFI(ESP8266)+GPRS(Air202)系统方案数据篇(云端电脑(Windows)安装配置数据库,使用本地Navicat for MySQL和手机APP 远程连接测试)
3-STM32物联网开发WIFI(ESP8266)+GPRS(Air202)系统方案数据篇(安装配置数据库,使用Navicat for MySQL和手机APP 连接测试) 根据前面的教程把软件复制到云 ...
- Ambari 使用 Hive View 异常处理
异常:进入Hive View提示user home check fail 详细日志:Service 'userhome' check failed: java.io.FileNotFoundExcep ...