ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

为Wpf敏捷开发做准备-Wpf实现Form表单1

2022-06-18 09:32:30  阅读:133  来源: 互联网

标签:Form element static typeof new Wpf 表单 public


前言:Form表单是比较常见的一种布局,Wpf一般使用Gird进行布局,但是代码会显得比较多比较乱,参照vue的Form表单,代码就比较简洁。

比如实现如图的编辑模板,您会想到用什么布局呢?

现在开始,我们来实现一个Wpf的Form表单,实现效果图如下:

第一步:实现Form的子元素FormItem,有个标头和内容项,使用HeaderedContentControl最合适不过了,代码如下:

public class FormItem : HeaderedContentControl
{
    static FormItem()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(FormItem), new FrameworkPropertyMetadata(typeof(FormItem), FrameworkPropertyMetadataOptions.Inherits));
    }
}

然后再拷贝一个HeaderedContentControl的样式,然后改写成自己的样式风格。

 <Style x:Key="AIStudio.Styles.FormItem" TargetType="{x:Type controls:FormItem}">
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Foreground" Value="{DynamicResource MahApps.Brushes.Text}"/>
        <Setter Property="MinHeight" Value="{DynamicResource DefaultControlHeight}" />
        <Setter Property="Padding" Value="{Binding RelativeSource={RelativeSource Mode=Self},Path=(controls:Form.ItemMargin)}"/>
        <Setter Property="HorizontalContentAlignment" Value="Right"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type controls:FormItem}">
                    <Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" UseLayoutRounding="False">
                        <Grid >
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=(controls:Form.HeaderWidth)}" />
                                <ColumnDefinition Width="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=(controls:Form.BodyWidth)}" />
                            </Grid.ColumnDefinitions>
                            <ContentPresenter x:Name="PART_Header"
                                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                ContentSource="Header" 
                                TextElement.Foreground="{TemplateBinding Foreground}"
                                Margin="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=(controls:Form.HeaderMargin)}"/>
                            <ContentPresenter
                                x:Name="PART_ContentPresenter"
                                Grid.Column="1"
                                HorizontalAlignment="Stretch"
                                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                ContentSource="Content"
                                Margin="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=(controls:Form.BodyMargin)}"/>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>      
    </Style>

具体使用也和HeaderedContentControl一样

<ac:FormItem Header="标题长度">
    <TextBox Margin="2" MinWidth="100" Style="{DynamicResource AIStudio.Styles.TextBox}"/>
</ac:FormItem>

第二步:实现Form,集成ItemsControl即可,但是我们要实现选中项,改成继承Selector。

public class Form : Selector
{
}

另外为了控制子元素的列头宽度和内容宽度,子项之间的间距,添加附加属性,这样只改变Form就能影响FormItem,比挨个设置FormItem方便。

#region AttachedProperty : HeaderWidthProperty
public static readonly DependencyProperty HeaderWidthProperty
    = DependencyProperty.RegisterAttached("HeaderWidth", typeof(GridLength), typeof(Form), new FrameworkPropertyMetadata(new GridLength(80d, GridUnitType.Pixel), FrameworkPropertyMetadataOptions.Inherits));

public static GridLength GetHeaderWidth(DependencyObject element) => (GridLength)element.GetValue(HeaderWidthProperty);
public static void SetHeaderWidth(DependencyObject element, GridLength value) => element.SetValue(HeaderWidthProperty, value);
#endregion

#region AttachedProperty : BodyWidthProperty
public static readonly DependencyProperty BodyWidthProperty
    = DependencyProperty.RegisterAttached("BodyWidth", typeof(GridLength), typeof(Form), new FrameworkPropertyMetadata(new GridLength(1, GridUnitType.Star), FrameworkPropertyMetadataOptions.Inherits));

public static GridLength GetBodyWidth(DependencyObject element) => (GridLength)element.GetValue(BodyWidthProperty);
public static void SetBodyWidth(DependencyObject element, GridLength value) => element.SetValue(BodyWidthProperty, value);
#endregion

#region AttachedProperty : OrientationProperty
public static readonly DependencyProperty OrientationProperty
    = DependencyProperty.RegisterAttached("Orientation", typeof(Orientation), typeof(Form), new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.Inherits));

public static Orientation GetOrientation(DependencyObject element) => (Orientation)element.GetValue(OrientationProperty);
public static void SetOrientation(DependencyObject element, Orientation value) => element.SetValue(OrientationProperty, value);
#endregion

#region AttachedProperty: ItemMarginProperty
public static readonly DependencyProperty ItemMarginProperty
    = DependencyProperty.RegisterAttached("ItemMargin", typeof(Thickness), typeof(Form), new FrameworkPropertyMetadata(new Thickness(3), FrameworkPropertyMetadataOptions.Inherits));
public static Thickness GetItemMargin(DependencyObject element) => (Thickness)element.GetValue(ItemMarginProperty);
public static void SetItemMargin(DependencyObject element, Thickness value) => element.SetValue(ItemMarginProperty, value);
#endregion     

#region AttachedProperty : HeaderMarginProperty
public static readonly DependencyProperty HeaderMarginProperty
    = DependencyProperty.RegisterAttached("HeaderMargin", typeof(Thickness), typeof(Form), new FrameworkPropertyMetadata(new Thickness(0, 0, 3, 0), FrameworkPropertyMetadataOptions.Inherits));

public static Thickness GetHeaderMargin(DependencyObject element) => (Thickness)element.GetValue(HeaderMarginProperty);
public static void SetHeaderMargin(DependencyObject element, Thickness value) => element.SetValue(HeaderMarginProperty, value);
#endregion

#region AttachedProperty : BodyMarginProperty
public static readonly DependencyProperty BodyMarginProperty
    = DependencyProperty.RegisterAttached("BodyMargin", typeof(Thickness), typeof(Form), new FrameworkPropertyMetadata(default(Thickness), FrameworkPropertyMetadataOptions.Inherits));

public static Thickness GetBodyMargin(DependencyObject element) => (Thickness)element.GetValue(BodyMarginProperty);
public static void SetBodyMargin(DependencyObject element, Thickness value) => element.SetValue(BodyMarginProperty, value);
#endregion

(在上面的FormItem的样式里面,用到了如上的附加属性) 另外注意:FrameworkPropertyMetadataOptions.Inherits。这样很关键,表示子元素都继承这个属性,设置在Form上,FormItem也生效。

第三步:如何实现Form的快速布局切换呢?大家都知道ItemsControl改变布局的属性是ItemsPanelTemplate,实现是StackPanel还是WrapPanel,或UniformGrid,都可以设置,如:

<ac:Form.ItemsPanel>
    <ItemsPanelTemplate>
        <WrapPanel/>
    </ItemsPanelTemplate>
</ac:Form.ItemsPanel>

但是这样还是不方便,设置属性才是最方便的,添加依赖性属性PanelType,设置类型,就实现布局切换。

public static readonly DependencyProperty PanelTypeProperty =
      DependencyProperty.Register("PanelType", typeof(FormPanelType), typeof(Form), new PropertyMetadata(FormPanelType.StackPanel, OnPanelTypeChanged));

    public FormPanelType PanelType
    {
        get
        {
            return (FormPanelType)GetValue(PanelTypeProperty);
        }
        set
        {
            SetValue(PanelTypeProperty, value);
        }
    }

    private static void OnPanelTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is Form form)
        {
            ItemsPanelTemplate panel = new ItemsPanelTemplate();
            if ((FormPanelType)e.NewValue == FormPanelType.StackPanel)
            {
                FrameworkElementFactory factory = new FrameworkElementFactory(typeof(StackPanel));
                factory.SetValue(StackPanel.OrientationProperty, Orientation.Vertical);
                panel.VisualTree = factory;
            }
            else if ((FormPanelType)e.NewValue == FormPanelType.WrapPanel)
            {
                FrameworkElementFactory factory = new FrameworkElementFactory(typeof(WrapPanel));
                panel.VisualTree = factory;
            }
            else if ((FormPanelType)e.NewValue == FormPanelType.UniformWrapPanel)
            {
                FrameworkElementFactory factory = new FrameworkElementFactory(typeof(UniformWrapPanel));
                factory.SetValue(UniformWrapPanel.ColumnsProperty, form.PanelColumns);
                panel.VisualTree = factory;
            }
            else if ((FormPanelType)e.NewValue == FormPanelType.UniformGrid)
            {
                FrameworkElementFactory factory = new FrameworkElementFactory(typeof(UniformGridEx));
                factory.SetValue(UniformGridEx.ColumnsProperty, form.PanelColumns);
                factory.SetValue(UniformGridEx.VerticalAlignmentProperty, VerticalAlignment.Top);
                panel.VisualTree = factory;
            }

            form.ItemsPanel = panel;
        }

    }

其中UniformWrapPanel和UniformGridEx是我实现的(具体代码就不贴出了,最后在源码地址大家下载查看),可以设置属性表示占据一个元素位置,还是占据两个,如部门的跨度设置为2时,占两个位置。

第四步:实现拖拽,可以实现用户自定义布局,只贴出了关键代码。

private void Form_Drop(object sender, DragEventArgs e)
{
    if (IsReadOnly)
        return;

    var pos = e.GetPosition(this);
    var result = VisualTreeHelper.HitTest(this, pos);
    if (result == null)
    {
        return;
    }

    //查找元数据
    var sourceItem = (e.Data.GetData(typeof(FormItem)) ?? e.Data.GetData(typeof(FormCodeItem))) as FormItem;
    if (sourceItem == null)
    {
        return;
    }

    //查找目标数据
    var targetItem = VisualHelper.FindParent<FormItem>(result.VisualHit);
    if (sourceItem == targetItem)
    {
        return;
    }

    if (targetItem == null)
    {
        AddItem(sourceItem);
    }
    else if (sourceItem.ParentForm != this)
    {
        InsertItem(sourceItem, targetItem);
    }
    else
    {
        ChangedItem(sourceItem, targetItem);
    }

    this.Items.Refresh();
}

里面还实现了左键按下防抖,按住一定时间才进行拖动,代码太多,请大家下载查看。

如何使用

<ac:Form x:Name="form" 
    VerticalAlignment="Top"
    DataContext="{Binding Base_User}"
    Margin="2">   
    <ac:FormItem Header="{Binding .,Converter={StaticResource DisplayNameConverter},ConverterParameter='UserName'}">
        <TextBox Text="{Binding UserName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" 
                     ac:ControlAttach.ClearTextButton="True"
                     Style="{DynamicResource AIStudio.Styles.TextBox.Underline}"/>
    </ac:FormItem>
    <ac:FormItem Header="{Binding .,Converter={StaticResource DisplayNameConverter},ConverterParameter='Password'}">
        <PasswordBox ac:PasswordBoxBindingBehavior.Password="{Binding Password,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" 
                    ac:ControlAttach.ClearTextButton="True"                                            
                    Style="{DynamicResource AIStudio.Styles.PasswordBox.Underline}"/>
    </ac:FormItem>
    <ac:FormItem Header="{Binding .,Converter={StaticResource DisplayNameConverter},ConverterParameter='Sex'}">
        <StackPanel Orientation="Horizontal">
            <RadioButton Content="男" IsChecked="{Binding Sex,Converter={ac:ConverterValueMapToBool Parameter=0},ConverterParameter=1}" Style="{DynamicResource AIStudio.Styles.RadioButton}"/>
            <RadioButton Content="女" IsChecked="{Binding Sex,Converter={ac:ConverterValueMapToBool Parameter=1},ConverterParameter=0}" Style="{DynamicResource AIStudio.Styles.RadioButton}"/>
        </StackPanel>
    </ac:FormItem>
    <ac:FormItem Header="{Binding .,Converter={StaticResource DisplayNameConverter},ConverterParameter='Birthday'}">
        <DatePicker SelectedDate="{Binding Birthday,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" 
                    ac:ControlAttach.ClearTextButton="True"  
                    Style="{DynamicResource AIStudio.Styles.DatePicker.Underline}"/>
    </ac:FormItem>
    <ac:FormItem Header="{Binding .,Converter={StaticResource DisplayNameConverter},ConverterParameter='DepartmentId'}" ac:UniformGridEx.Span="2">
        <ac:TreeSelect SelectedValue="{Binding DepartmentId,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" 
                    ItemsSource="{ac:ControlBinding Departments}"
                    DisplayMemberPath="Text"
                    SelectedValuePath="Value"
                    ac:ControlAttach.ClearTextButton="True"
                    Style="{DynamicResource AIStudio.Styles.TreeSelect.Underline}">
            <ac:TreeSelect.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Text}" VerticalAlignment="Center" HorizontalAlignment="Left"/>
                    </StackPanel>
                </HierarchicalDataTemplate>
            </ac:TreeSelect.ItemTemplate>
        </ac:TreeSelect>
    </ac:FormItem>
    <ac:FormItem Header="{Binding .,Converter={StaticResource DisplayNameConverter},ConverterParameter='RoleIdList'}">
        <ac:MultiComboBox 
                    ac:CustomeSelectionValues.SelectedValues="{Binding RoleIdList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"                                               
                    ItemsSource="{ac:ControlBinding RolesList}"
                    DisplayMemberPath="Text" 
                    SelectedValuePath="Value"
                    ac:ControlAttach.ClearTextButton="True"
                    Style="{DynamicResource AIStudio.Styles.MultiComboBox.Underline}"/>
    </ac:FormItem>
    <ac:FormItem Header="{Binding .,Converter={StaticResource DisplayNameConverter},ConverterParameter='SelectedDuty'}">
        <ComboBox SelectedValue="{Binding SelectedDuty,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" 
                    ItemsSource="{ac:ControlBinding Duties}"
                    DisplayMemberPath="Text" 
                    SelectedValuePath="Value" 
                    ac:ControlAttach.ClearTextButton="True"
                    Style="{DynamicResource AIStudio.Styles.ComboBox.Underline}"/>
    </ac:FormItem>
    <ac:FormItem Header="{Binding .,Converter={StaticResource DisplayNameConverter},ConverterParameter='Email'}">
        <TextBox Text="{Binding Email,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" 
                    ac:ControlAttach.ClearTextButton="True"
                    Style="{DynamicResource AIStudio.Styles.TextBox.Underline}"/>
    </ac:FormItem>
    <ac:FormItem Header="{Binding .,Converter={StaticResource DisplayNameConverter},ConverterParameter='PhoneNumber'}">
        <TextBox Text="{Binding PhoneNumber,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" 
                    ac:ControlAttach.ClearTextButton="True"
                    Style="{DynamicResource AIStudio.Styles.TextBox.Underline}"/>
    </ac:FormItem>
    <ac:FormItem>
        <Button  Content="提交" Command="{ac:ControlBinding SubmitCommand}" CommandParameter="{Binding .}"
                    Style="{DynamicResource AIStudio.Styles.Button}"/>
    </ac:FormItem>
</ac:Form>

最后老规矩,上源码地址,在Controls下的Form文件夹中。

https://gitee.com/akwkevin/AI-wpf-controls

标签:Form,element,static,typeof,new,Wpf,表单,public
来源: https://www.cnblogs.com/akwkevin/p/16387680.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有