wpf自定义组件
wpf的学习,xaml写原生的控件,设置样式,或把样式抽离出来放到全局app.xaml,这些百度学习。
这里着重讲下,对于一些控件没有的样式怎么自定义一个组件,比如下面讲解自定义一个button实现圆角功能 (目标,给原有button添加新属性,并且要可以在标签绑定此值)。
技术细节,button里面加一个boder,因为boder有圆角属性CornerRadius,可以给自定义button加此属性,属性绑定值时传递到boder标签中。
未完成.....
另外:
1.在xaml写style样式和触发器 (按钮背景颜色触发器要生效必须给button写ControlTemplate)
触发器: https://www.cnblogs.com/LXLR/p/17653745.html
<Window x:Class="WpfApp3.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp3" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.Resources> <Style TargetType="Button"> <Setter Property="Width" Value="60"></Setter> <Setter Property="Height" Value="120"></Setter> </Style> <Style x:Key="button1_style" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}"> <Setter Property="Background" Value="Green"></Setter> <Setter Property="FontSize" Value="20"></Setter> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True" > <Trigger.Setters > <Setter Property="FontSize" Value="12"></Setter> <Setter Property="Foreground" Value="Red"></Setter> <Setter Property="Background" Value="Red"></Setter> </Trigger.Setters> </Trigger> <Trigger Property="IsPressed" Value="True"> <Trigger.Setters > <Setter Property="FontSize" Value="30"></Setter> <Setter Property="Foreground" Value="Yellow"></Setter> <Setter Property="Background" Value="Yellow"></Setter> </Trigger.Setters> </Trigger> </Style.Triggers> </Style> <Style TargetType="CheckBox"> <Style.Triggers> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsChecked" Value="true"/> <Condition Property="Content" Value="HelloWorld"/> </MultiTrigger.Conditions> <MultiTrigger.Setters> <Setter Property="FontSize" Value="40"/> <Setter Property="Foreground" Value="Orange"/> <Setter Property="Background" Value="Red"/> </MultiTrigger.Setters> </MultiTrigger> </Style.Triggers> </Style> </Window.Resources> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions > <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Button Content="12" Style="{DynamicResource button1_style}"></Button> <StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal"> <CheckBox Content="HelloWorld" ></CheckBox> </StackPanel> <StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal"> <Button Content="12" Style="{StaticResource button1_style}"></Button> <Button Content="123"> <Button.Style> <Style TargetType="Button"> <Setter Property="Background" Value="Red"></Setter> <Setter Property="Foreground" Value="White"/> <Setter Property="FontSize" Value="16"/> <Setter Property="Width" Value="100"/> <Setter Property="Height" Value="50"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="BlanchedAlmond"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background" Value="Cyan"/> </Trigger> </Style.Triggers> </Style> </Button.Style> </Button> <Button Content="Click Me"> <Button.Template> <ControlTemplate TargetType="{x:Type Button}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> </ControlTemplate> </Button.Template> <Button.Style> <Style TargetType="Button"> <Setter Property="Background" Value="Blue"/> <Setter Property="Foreground" Value="White"/> <Setter Property="FontSize" Value="16"/> <Setter Property="Width" Value="100"/> <Setter Property="Height" Value="50"/> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="Red"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background" Value="Cyan"/> </Trigger> </Style.Triggers> </Style> </Button.Style> </Button> </StackPanel> </Grid> </Window>
2.引入样式文件在项目中(资源字典)
3.控件模版、组件自定义模板重写 ControlTemplate
<Button > <Button.Content> <StackPanel Orientation="Vertical" Height="90"> <Button Content="1" Height="20"></Button> <Button Content="2" Height="20"></Button> <Button Content="3" Height="20"></Button> <Border> <TextBox>text</TextBox> </Border> </StackPanel> </Button.Content> </Button>
<Button Content="内容" Width="100" Height="80" Background="Red" FontSize="24" Foreground="BurlyWood"> <Button.Template> <ControlTemplate TargetType="{x:Type Button}"> <Border x:Name="border1" Background="{TemplateBinding Background}" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" > <TextBlock Text="{TemplateBinding Content}"></TextBlock> <!--ContentPresenter></ContentPresenter--> <!-- 原来组件的内容--> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="border1" Property="Background" Value="Yellow"></Setter> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Button.Template> </Button>
4.数据模版 DataTemplate
/// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); List <Color> test = new List <Color> (); test.Add(new Color() { code = "#ffb6c1", name = "钱粉色" }); test.Add(new Color() { code = "#9400D3", name = "暗紫罗兰" }); test.Add(new Color() { code = "#E6E6FA", name = "熏衣草淡紫" }); test.Add(new Color() { code = "#000080", name = "海军蓝" }); list1.ItemsSource = test; } } public class Color { public string name { get; set; } public string code { get; set; } }
<ListBox x:Name="list1"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Border Background="{Binding code}" Width="60" Height="20"> <TextBlock Margin="2,2" Text="{Binding name}"></TextBlock> </Border> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
5.绑定
a.两组件值绑定 (Slider和TextBox)
<StackPanel Grid.Row="2" Grid.Column="0" Orientation="Vertical"> <Slider x:Name="slider1" Margin="5"></Slider> <TextBox Text="{Binding ElementName=slider1, Path=Value,Mode=TwoWay}"></TextBox> </StackPanel>
b.后台model类属性值绑定
界面要设置 : this.DataContext = viewModel;
界面:
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal"> <TextBlock>姓名:</TextBlock> <TextBox Width="100" Height="30" Text="{Binding name}"></TextBox> <TextBlock>密码:</TextBlock> <TextBox Width="100" Height="30" Text="{Binding password}"></TextBox> </StackPanel>
界面的cs,代码把dataContext设置上。
/// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = new mainViewModel() { name="name11", password = "password11"};//给一个初始值 } }
ViewModel,练习view和model, 暂时把实体属性,写到viewModel
namespace WpfApp3.viewModel { class mainViewModel { public string name { get; set; } public string password { get; set; } } }
6.通知更新(INotifyPropertyChanged) 即使继承INotifyPropertyChanged接口,使用事件修改ProPertyChangedEventArgs
a.版本一,不抽离方法写法
class mainViewModel :INotifyPropertyChanged { private string _name; public string name { get { return _name; } set { _name = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(name))); } } private string _password; public string password { get { return _password; } set { _password = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("password")); } } public event PropertyChangedEventHandler PropertyChanged; }
b.版本二,变更抽离到一个方法中 (让多个实体model获得通知能力,就抽离成一个方法)
class mainViewModel :INotifyPropertyChanged { private string _name; public string name { get { return _name; } set { _name = value; OnPropertyChanged(); } } private string _password; public string password { get { return _password; } set { _password = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; //加特性,可以让不加参数自动获取属性名 public void OnPropertyChanged([CallerMemberName]string propertyName="") { PropertyChanged?.Invoke(this , new PropertyChangedEventArgs(propertyName)); } }
7.命令(ICommand)
实例化ICommand接口的,实现类初始化方法传参数(方法名),接口实现类内部再传入一个委托Action在接口执行的方法中调用委托,模版的命令写ICommand类型变量名.
RelayCommand.cs
public class RelayCommand : ICommand { private readonly Action<object> _execute; private readonly Func<object, bool> _canExecute; public RelayCommand(Action<object> execute) : this(execute, null) { } public RelayCommand(Action<object> execute, Func<object, bool> canExecute) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public bool CanExecute(object parameter) { return _canExecute == null || _canExecute(parameter); } public void Execute(object parameter) { _execute(parameter); } }
然后在 mainVM.cs中,定义方法变量,委托调用一个业务方法.
public class MyViewModel : INotifyPropertyChanged { public ICommand MyCommand { get; set; } public MyViewModel() { MyCommand = new RelayCommand(ExecuteCommand); //初始化时把命令变量和方法,关联到一起 } //模板为MyConnand命令,业务执行方法为ExecuteCommand private void ExecuteCommand(object parameter) { // 业务执行操作 } // 实现INotifyPropertyChanged接口的代码省略 }
8.mvvm框架 (MVVMLight和)