WPF使用总结
ListboxItemContainer样式
一般items控件的项模板 很容易 设置DataTemplate就可以了,比如listbox 。但是在选中和失去焦点的时候 却是Windows自带的那种 蓝色下拉框选中效果 ,为了更改这种效果,首先尝试定义控件的itemcontainerstyle:
1
2 3 <Setter Property="ItemContainerStyle"> 4 <Setter.Value> 5 <Style TargetType="ListBoxItem"> 6 <Setter Property="Background" Value="Azure"/> 7 <Style.Triggers> 8 <Trigger Property="IsSelected" Value="true"> 9 <Setter Property="Background" Value="Red"/> 10 </Trigger> 11 <Trigger Property="IsMouseOver" Value="true"> 12 <Setter Property="Background" Value="Red"/> 13 </Trigger> 14 </Style.Triggers> 15 </Style> 16 </Setter.Value> 17 </Setter>
试了下结果是费尽各种方法都无法达到效果。最后在stackover flow上找到了一个答案,
https://stackoverflow.com/questions/5361698/wpf-trigger-for-listboxitem-isselected-not-working-for-background-property
那就是控件本身有一个默认Template wpf中所有控件都有默认Template。由于默认控件里也有IsSelected的trigger 优先级高于样式外面定义的 所以导致外面的IsSelected trigger失效,所以,那就只能定义Template 才能达到效果。
the standard ControlTemplate of ListItem to define it's own triggers which seem to take precendence over triggers defined by the style
重新定义Template来达到效果:
1
2 3 <Setter Property="ItemContainerStyle"> 4 <Setter.Value> 5 <Style TargetType="ListBoxItem"> 6 <Setter Property="Background" Value="Azure"/> 7 <Setter Property="Template"> 8 <Setter.Value> 9 <ControlTemplate TargetType="ListBoxItem"> 10 <Border Name="border" BorderThickness="0" Background="Aquamarine" Margin="5" CornerRadius="5"> 11 <ContentPresenter/> 12 </Border> 13 <ControlTemplate.Triggers> 14 <Trigger Property="IsSelected" Value="true"> 15 <Setter TargetName="border" Property="Background" Value="red"/> 16 </Trigger> 17 </ControlTemplate.Triggers> 18 </ControlTemplate> 19 </Setter.Value> 20 </Setter> 21 </Style> 22 </Setter.Value> 23 </Setter>
注意 在模板内部 的trigger 是可以通过name访问的 比如上例的 TargetName=”border” 。 itemsTemplate不设置Background则认为背景色为透明 containerstyle可以替换这部分的颜色 但是containerstyle只作用于每个itemsTemplate的外围区域并不能覆盖它 之上 。还一种方式就是在merged Resources里写一些系统内置的神秘代码:就是说元素绘制出来的东西遇到了下拉框选型IsSelected 的那种特定系统色后 我们就把替换成我们想要的颜色:
1 <ListBox Name="studentList" Style="{StaticResource myStyle}" ItemsSource="{Binding }" > 2 <ListBox.Resources> 3 <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Red" /> 4 <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Red" /> 5 </ListBox.Resources> 6 </ListBox>
虽然看着是神秘代码但是也不是想象的那么神秘,通过Wpf的书上我们就知道 只不过Key绑定了一个名为HighlightBrushKey系统静态变量而已,说白了还是根据Windows主题风格来的 说不定换到哪个主题这个颜色就不起作用了 所以还是推荐模板的方式好点吧。通过查看SystemColors类就可以知道还有哪些其它颜色。项目中到处用到的滚动条样式也使用了mergedResources方式:
1 <ListBox.Resources> 2 <!--滚动条整体样式--> 3 <Style TargetType="{x:Type ScrollBar}"> 4 <Setter Property="SnapsToDevicePixels" Value="True"/> 5 <Setter Property="OverridesDefaultStyle" Value="true"/> 6 <Style.Triggers> 7 <Trigger Property="Orientation" Value="Horizontal"> 8 <Setter Property="Width" Value="Auto"/> 9 <Setter Property="Height" Value="10" /> 10 </Trigger> 11 <Trigger Property="Orientation" Value="Vertical"> 12 <Setter Property="Width" Value="10"/> 13 <Setter Property="Height" Value="Auto" /> 14 <Setter Property="Template" 15 Value="{StaticResource VerticalScrollBar}" /> 16 </Trigger> 17 </Style.Triggers> 18 </Style> 19 </ListBox.Resources>
还有这玩意儿:ItemContainerStyleSelector 你要自己写c#代码 继承StyleSelector 在方法里返回一个Style对象 用于制作那种表格的奇数行偶数行 变换样式,在我们项目里由于没有用到 这里只是提一下。
对于Items控件 在 Template里 显示定义ScrollViewer 来控制横向 或者纵向滚动:
1 <Setter Property="Template"> 2 <Setter.Value> 3 <ControlTemplate> 4 <Border BorderBrush="Blue" BorderThickness="1"> 5 <ScrollViewer HorizontalScrollBarVisibility="Visible"> 6 <ItemsPresenter></ItemsPresenter> 7 </ScrollViewer> 8 </Border> 9 </ControlTemplate> 10 </Setter.Value> 11 </Setter>
WrapPanel是可以按像素滚动的,StackPanel 按整行滚动
items控件的模板定义
Items控件是那种带多个子项的控件 比如最常见的就是listbox Items控件的子项的呈现要想完全更改外观通过itemTemplate ,否则只能简单的改改背景色之类的 还是常见的下拉框那种外观。
几个重要属性和标签的对应关系:
itemTemple对应 Datatemple 用于定义每个项的外观
Temple对应ControlTemplate 用于定义大的控件外观
<ItemsPresenter></ItemsPresenter>代表子项的内容渲染
ItemsPanel 对应ItemsPanelTemplate代表子项的容器面板
containerStyle用于控制子项的容器的外观 上面已经说了
一个简易的listbox的 外观更改 和绑定示例:
1 <Style x:Key="myStyle" TargetType="ListBox"> 2 <Setter Property="Template"> 3 <Setter.Value> 4 <ControlTemplate> 5 <Border BorderBrush="Blue" BorderThickness="1"> 6 <ScrollViewer HorizontalScrollBarVisibility="Visible"> 7 <ItemsPresenter></ItemsPresenter> 8 </ScrollViewer> 9 </Border> 10 </ControlTemplate> 11 </Setter.Value> 12 </Setter> 13 <Setter Property="ItemTemplate"> 14 <Setter.Value> 15 <DataTemplate> 16 <Border BorderBrush="Red" BorderThickness="1" Margin="10"> 17 <TextBlock Text="{Binding Age}"></TextBlock> 18 </Border> 19 </DataTemplate> 20 </Setter.Value> 21 </Setter> 22 <Setter Property="ItemsPanel"> 23 <Setter.Value> 24 <ItemsPanelTemplate> 25 <StackPanel Orientation="Vertical" Margin="0,0,35,0" ></StackPanel> 26 </ItemsPanelTemplate> 27 </Setter.Value> 28 </Setter> 29 </Style>
还有就是ControlTemplate最好要写上targettype 否则很多属性trigger都会认为无效 无法编译通过。注意DataTemplate也并不一定要写在一个Style里面 你可以自由组合 ,在几个控件中使用。
关于复杂模板 样式的应用
数据驱动 和样式模板这些是Wpf的精髓,这种设计大大弱化winform里的“自定义控件”其实就是手动渲染绘制。Wpf的基础是 最外面为我们看得见的控件布局 称之为logicTree 。每种控件是有一些自己的规则限定的比如button的content只能是文本。控件可以通过属性更改外观 。style是属性的打包。Style可以继承。每个控件里面可以定义Template来完全更改控件的外观。Wpf已经为我们预先定义好了一些控件的Template ,比如button 默认的就是那种灰色的方形的样式。WPF整体的从窗体到控件 的渲染是一个递归系统。logicTree是主干 visualtree是枝叶。logicTree是固定的visualtree是不固定的 根据绑定的数据动态生成 ,说白了就是Template。有时候我们想要自定义一个Template 总是无从着手 因为太复杂了,看着是一个按钮 里面由各种线条和填充构成 标签写错了 wpf又不认,这时候我们可以通过wpf自带的默认Template来学习 在它基础上更改 修改。输出默认模板的c#代码示例,比如有个名为btn1的button:
1 string xamlString = XamlWriter.Save(btn1.Template); 2 FileStream fs= File.Create("aa.txt"); 3 StreamWriter sw = new StreamWriter(fs); 4 sw.Write(xamlString); 5 sw.Close();
使用下面这段代码可以简单的查看一个控件的visualTree :
1 public void ShowVisualTree(int deepenLeve, DependencyObject refrenceObj) 2 { 3 for (int i = 0; i < deepenLeve; i++) 4 { 5 Console.Write("\t"); 6 } 7 if (refrenceObj is TextBlock) 8 { 9 TextBlock txblock = refrenceObj as TextBlock; 10 txblock.Background = Brushes.Red; 11 } 12 Console.Write(refrenceObj.ToString() + "\r\n"); 13 int childCount = VisualTreeHelper.GetChildrenCount(refrenceObj); 14 for (int i = 0; i < childCount; i++) 15 { 16 ShowVisualTree(deepenLeve + 1, VisualTreeHelper.GetChild(refrenceObj, i)); 17 } 18 }
1 ShowVisualTree(0, btn1);
运行可以看到 一个button在win10默认风格下 由一个border一个 contentpresenter 一个textblock一层层嵌套构成。当然在这里面你也可以操作visualtree里面的元素比如上面把textBlock的背景改成了红色。
还有需要注意的是 如果你手动写这样的代码:
1 <ListBox> 2 <ListBoxItem>111</ListBoxItem> 3 <ListBoxItem>222</ListBoxItem> 4 </ListBox>
他并不能应用listbox的itemsTemplate模板 ,并不能, menuItem也是如此。你必须要定义一个targettype=ListBoxItem的模板才行,然后逐步应用 ,在WPF中这是相当复杂的,所以比较好的方式是用上面的方法 输出visualTree,然后在基础之上慢慢修改。
带水印文本框 自定义checkbox
Wpf开发 时常会用到网页应用程序的那种样式 ,比如搜索框 在没有输入任何关键字的时候显示 “请输入模板名称” 的水印 以提示用户。实现方式:定义textbox的模板 设置触发器 在文本框内容为空的情况下 设置Background为一个带文本的virtualBrush
1 2 <TextBox Text="dfdf" Height="30" Name="nihao" > 3 <TextBox.Style> 4 <Style TargetType="TextBox"> 5 <Setter Property="Template"> 6 <Setter.Value> 7 <ControlTemplate TargetType="TextBox"> 8 <Border BorderThickness="1" BorderBrush="Blue"> 9 <TextBlock Name="textContent" Text="{TemplateBinding Text}"></TextBlock> 10 </Border> 11 <ControlTemplate.Triggers> 12 <MultiTrigger > 13 <MultiTrigger.Conditions> 14 <Condition Property="Text" Value=""/> 15 </MultiTrigger.Conditions> 16 <Setter Property="Background" TargetName="textContent"> 17 <Setter.Value> 18 <VisualBrush AlignmentX="Left" AlignmentY="Top" Stretch="None"> 19 <VisualBrush.Visual> 20 <TextBlock Width="100" Height="20">请输入内容</TextBlock> 21 </VisualBrush.Visual> 22 </VisualBrush> 23 </Setter.Value> 24 </Setter> 25 </MultiTrigger> 26 </ControlTemplate.Triggers> 27 </ControlTemplate> 28 </Setter.Value> 29 </Setter> 30 </Style> 31 </TextBox.Style> 32 </TextBox>
注意 一般我们应用trigger 都是应用到style.triggers层, 其实也可应用到ControlTemplate层 只不过setter的时候你需要改变哪一个元素 你要指定targetName 否则wpf无法识别。其实是一样的 你应用到哪一层则在哪一层起作用。上面实现了一个简易的水印。就像上面的如果trigger是style层的自然就是更改的最外层应用style的textbox的Background 。一个checkBox的自定义样式:
1 <CheckBox > 2 <CheckBox.Template> 3 <ControlTemplate TargetType="CheckBox"> 4 <StackPanel Orientation="Horizontal"> 5 <Rectangle Name="breakRectangle" Stroke="Red" StrokeThickness="2" Width="20" Height="20"> 6 <Rectangle.Fill> 7 <SolidColorBrush Color="Azure"></SolidColorBrush> 8 </Rectangle.Fill> 9 </Rectangle> 10 <ContentPresenter></ContentPresenter> 11 </StackPanel> 12 <ControlTemplate.Resources> 13 <SolidColorBrush x:Key="redBrush" Color="Red"/> 14 </ControlTemplate.Resources> 15 <ControlTemplate.Triggers> 16 <Trigger Property="IsChecked" Value="True"> 17 <Setter TargetName="breakRectangle" Property="Fill" Value="{StaticResource ResourceKey=redBrush}"></Setter> 18 </Trigger> 19 </ControlTemplate.Triggers> 20 </ControlTemplate> 21 </CheckBox.Template> 22 复选框自定义模板 23 </CheckBox>
Items控件的数据绑定
我们写{bind} 其实就是用一个binding类对象作为依赖属性的值 ,没什么好神奇的 。只不过大括号这种{bind } 是wpf自带的使用内置的converter 就可以转换解析 然后在UI上显示出对应的东西。跟Asp.Net里一样 绑定 无非就确定三个东西 ,1数据源 2绑数据源的哪个字段 也就是path。 3绑到UI的什么地方 。
第1,2步:
1 Binding binding = new Binding("Value") { Source = this.myslider };
其中Value是绑的哪个字段也就是path ,Source代表数据源 。
第3步:
1 this.myTxt.SetBinding(TextBox.TextProperty, binding);
代表文本框的text属性 来显示 myslider的Value
对于平常业务的情况其实我们大都是直接这样写:
1 <TextBox Text="{Binding Name}"></TextBox>
对于1 数据源 其实在mvvm模式页面载入的时候就确定了 ,应用binding的时候会自动去找控件本身的DataContext作为数据源 找不到再去找页面this.DataContext 作为数据源。对于3 绑定到UI的什么地方 Text="" 就足以说明了 。所以直接<TextBox Text="{Binding Name}"></TextBox> 代表绑定当前数据上下文的Name字段到文本框的文本显示。每个控件本身都有个dataContext 代表数据上下文 ,但是对于Items控件平常我们都没有去设置他。只设置ItemsSource 就可以了 。ItemsSource代表控件自身的子项绑定时的数据源 为一个集合。在控件的DataTemple的时候自动的以集合的单个元素为输出 比如itemsSource=newList<Person>() 那么dataTemplate里一个文本框 text=”{binding Name}”则绑定的是每个person.Name 属性。 如果自身没有设置dataContext 会自动从该控件的上一级去寻找 。最终从整个窗体的根部也就是this.dataContext 寻找数据绑定对象 如果再没有那窗体上就不会有任何项目显示。 题外话:这都是依赖属性的功劳,依赖属性可以向上一级一级的形成binding链,依赖属性的精髓是“依赖” ,神奇之处在于我身上显示的这个值可以是我自己的也可以不是我自己的 别人变了我还能得到实时更新,说白了就是你搞这种属性 好处就是他的值可以应用{binding} Wpf的UI属性大都是依赖属性,我们平常写个Person类需要让他动态绑动态更新么 不需要 ,当然你要他动态更新绑定更新也是可以的 虽然不是UI对象,只要你有业务需求。但是平常业务中我们用的也并不多 都是使用继承NotifyPropertyChanged 的方式。默认绑定上去就是双向的。对于绑定集合 ,在增加项 删除项的时候 界面上并不会自动变动 最好使用ObservableCollection。对于converter个人觉得平时用的并不多。绑定的时候也可以用format 来格式化:
1 Text="{Binding StringFormat=共{0}个模板,ElementName=moduleList,Path=Items.Count}"
另外 绑定元素也可以有多种灵活的运用。下面这种通过listBox有没有数据项来决定 元素的启用与禁用:
1 IsEnabled="{Binding ElementName=stuctList,Path=HasItems}"
还可以这样:
1 Text="{Binding ElementName=stuctList,Path=SelectedItem.Name}"
直接显示选中项的数据对象的字段值。
把某个相对元素的属性绑定到本元素:
1 Text="{Binding RelativeSource={RelativeSource AncestorType=StackPanel,AncestorLevel=1},Path=Name}"
图片清晰 半透明背景 和遮罩弹出层。
在wpf中普通的Image对象是 不会发生图片模糊的情况的,只需要设置 Stretch="None" 。图片即会按照原始大小显示。但是如果布局复杂了,最简单的就是 在外面套一个border元素 ,程序运行时图片出现模糊,准确的说是图片发虚 尤其是有线条轮廓的那种图片。解决方式: 为外层的元素添加 UseLayoutRounding="True"。 Wpf界面是矢量的 UseLayoutRounding为true 元素在渲染时会进行像素对齐。
还有就是设置半透明背景 只需要设置Background值为argb形式就可以了 ,如果设置opticity的话整个元素都会变为半透明。遮罩弹出层其实也很简单 最外围使用grid包起来 弹出层整个一个半透明grid层 里面包着UI Grid默认具有铺满布局的效果,grid里面的控件如果不设置 row 和col 默认具有重叠布局 也就是一个控件可以重叠在另一个上面,由此可以达到遮罩弹出层的效果。
1 <Grid> 2 <Button HorizontalAlignment="Left" VerticalAlignment="Top" Width="80" Height="30" Click="Button_Click_1"> 打开弹出层</Button> 3 <Grid Background="#7F000000" Name="grid1" > 4 <Button Width="80" Height="30" Click="Button_Click" >关闭弹出层</Button> 5 </Grid> 6 </Grid>
在按钮事件里设置 grid1的Visible属性 可进行弹出和关闭。
关于列表的右键菜单
在易电助手3.0项目中我们需要根据右键点的项动态更改右键菜单哪个显示与不显示
我们一般设置一个ContextMenu 为资源 默认模式是共享模式 就是说这个地方变了另外一个地方也变了。
设置ItemsTemplate里的最大的元素的 ContextMenu为指定菜单就可以了 ,然后在PreviewMouseRightButtonDown事件里 找到元素 的DataContext 即找到了当前点的是哪个数据对象
1 Border curCtr = sender as Border; 2 if (curCtr == null) 3 return; 4 var currentStruct = curCtr.DataContext as ModuleStruct;
还有我们在非button控件设置鼠标左键双击事件的方式为 设置 PreviewMouseLeftButtonDown事件。然后在代码里判断 e.ClickCount>=2 则为双击。
控件上写依赖属性
在控件上挂载依赖属性有时候可以达到一些意想不到的效果,并且这个依赖属性可以不是他的父元素控件的值:
1 <Button Height="30" local:ControlHelper.Angle="10" Width="50" > 2 <Button.Template> 3 <ControlTemplate> 4 <Border BorderThickness="1" BorderBrush="Blue"> 5 <TextBox Text="{Binding RelativeSource={RelativeSource TemplatedParent},Path=(local:ControlHelper.Angle)}"></TextBox> 6 </Border> 7 </ControlTemplate> 8 </Button.Template> 9 </Button>
当然在页面上你必须引入local的命名空间 并且在controlHelper里实现Angle这个依赖属性:
1 xmlns:local="clr-namespace:WpfApplication2"
1 public class ControlHelper : DependencyObject 2 { 3 public static int GetAngle(DependencyObject obj) 4 { 5 return (int)obj.GetValue(AngleProperty); 6 } 7 public static void SetAngle(DependencyObject obj, int value) 8 { 9 obj.SetValue(AngleProperty, value); 10 } 11 12 // ... 13 public static readonly DependencyProperty AngleProperty = 14 DependencyProperty.RegisterAttached("Angle", typeof(int), typeof(ControlHelper), new PropertyMetadata(0, OnAngleChanged)); 15 16 17 private static void OnAngleChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 18 { 19 var element = obj as UIElement; 20 if (element != null) 21 { 22 element.RenderTransformOrigin = new Point(0.5, 0.5); 23 element.RenderTransform = new RotateTransform(double.Parse(e.NewValue.ToString())); 24 } 25 } 26 27 }
上面为控件添加了angle属性来达到控件旋转的效果。