终于开始写第二篇了... 加油加油,我要坚持写完这个系列...
从这一篇开始,我会从项目的流程和结构角度来介绍,中间穿插介绍遇到的问题和坑。
开始!
(本篇主要介绍登录页和主界面的框架布局)
项目情况说明
主要功能:对员工信息进行增删改查,生成特定格式的评估表单并打印,用户管理(可能有多个使用者通过不同的用户名和密码登录,需要管理)
用户:高管以及人资部门的员工。前者通过程序查看员工的详细信息不进行其他操作,后者通过程序来编辑和管理员工的信息。
部署方式:我们把程序部署在公司人资部的几台电脑上,把员工信息数据库部署在公司服务器上。(不宜把这样的数据库放在个人电脑上,一是不安全,二是不能满足绝大多数时间都能提供服务的要求)
程序和界面逻辑
首先需要登录,登录成功后,可进入“人事管理”、“用户管理”、“数据备份”、“帮助页面”。重点说一下人事管理页面,在此页面中可看到员工的信息表,并可查看每个员工的详细信息,以及进行添加、修改和删除操作。
登录界面
WPF 的界面通过拖拽就可以生成,我的做法是先拖拽出一个大致的界面,再通过 visual studio 侧边的属性栏或者修改 XAML 代码来进行更精细的调节,比如控制字号、颜色或对齐方式等。经过一顿折腾,做出来了如下的界面:(图标来自 Iconfont,阿里的矢量图标库)
开发过程中,遇到了许多大大小小的问题,依据回忆依次记录如下...
- 如何引用这些图标?
百度这个问题,可以有好几种解决方式,这里给出两种比较简单的,一是使用绝对路径,也就是在 image 标签中加上 Source="C:\Users\admin\Desktop\icon\password.png"
这样的路径说明就可以。但是这样的话相当于写死了代码,不是很推荐。我最终使用的是第二种方式,即相对路径。先在项目目录下新建一个文件夹并右键添加现有项,然后用如图所示的方式来进行引用即可。
- 用户名使用的是 WPF 自带的 TextBox 控件,如何显示灰字提示?
在 XAML 的TextBox中加上如下代码:
<TextBox.Resources>
<VisualBrush x:Key="HelpBrush"
Opacity="0.4"
Stretch="None"
AlignmentX="Left">
<VisualBrush.Visual>
<TextBlock Text="请输入用户名"
FontSize="16" />
</VisualBrush.Visual>
</VisualBrush>
</TextBox.Resources>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Text"
Value="">
<Setter Property="Background"
Value="{StaticResource HelpBrush}" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
- 密码框使用的是 WPF 自带的 PasswordBox 控件,但是由于提供给我们的属性有限,它并不能像 TextBox 那样设置提示信息,怎么办?
答案是只能自己实现一个...好在 Stackoverflow 上已经有人解决了这个问题,首先我们需要在 XAML 对应的 .cs 文件中的类后面加上下面的类和方法:
public class PasswordBoxMonitor : DependencyObject
{
public static bool GetIsMonitoring(DependencyObject obj)
{
return (bool)obj.GetValue(IsMonitoringProperty);
}
public static void SetIsMonitoring(DependencyObject obj, bool value)
{
obj.SetValue(IsMonitoringProperty, value);
}
public static readonly DependencyProperty IsMonitoringProperty =
DependencyProperty.RegisterAttached("IsMonitoring", typeof(bool), typeof(PasswordBoxMonitor), new UIPropertyMetadata(false, OnIsMonitoringChanged));
public static int GetPasswordLength(DependencyObject obj)
{
return (int)obj.GetValue(PasswordLengthProperty);
}
public static void SetPasswordLength(DependencyObject obj, int value)
{
obj.SetValue(PasswordLengthProperty, value);
}
public static readonly DependencyProperty PasswordLengthProperty =
DependencyProperty.RegisterAttached("PasswordLength", typeof(int), typeof(PasswordBoxMonitor), new UIPropertyMetadata(0));
private static void OnIsMonitoringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var pb = d as PasswordBox;
if (pb == null)
{
return;
}
if ((bool)e.NewValue)
{
pb.PasswordChanged += PasswordChanged;
}
else
{
pb.PasswordChanged -= PasswordChanged;
}
}
static void PasswordChanged(object sender, RoutedEventArgs e)
{
var pb = sender as PasswordBox;
if (pb == null)
{
return;
}
SetPasswordLength(pb, pb.Password.Length);
}
}
接下来返回到 XAML 文件中,在开头的地方(我们定义的第一个控件之前)加上下面一段定义 PasswordBox 属性的代码:
<Window.Resources>
<Style x:Key="{x:Type PasswordBox}"
TargetType="{x:Type PasswordBox}">
<Setter Property="DatabaseProject:PasswordBoxMonitor.IsMonitoring"
Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type PasswordBox}">
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
SnapsToDevicePixels="true">
<Grid>
<ScrollViewer x:Name="PART_ContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<TextBlock Text="请输入密码"
Margin="4, 6, 0, 0"
Foreground="#999999"
FontSize="16"
Visibility="Collapsed"
Name="txtPrompt" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled"
Value="false">
<Setter TargetName="Bd"
Property="Background"
Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
<Trigger Property="DatabaseProject:PasswordBoxMonitor.PasswordLength"
Value="0">
<Setter Property="Visibility"
TargetName="txtPrompt"
Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
虽然看着多,但是思路还是很清楚的,相当于先写个类,再定义属性,就可以在属性中设置灰色的提示文字了。
除了这些问题,登录时还需要与数据库内容进行比较,从而验证用户名和密码是否正确匹配。这部分内容现在先不写,准备放到后面和其他数据库操作一起讲 : )
主界面
主界面大致长这样:(不要嫌弃它太空,这是个演示...)
可以将它作为一个欢迎页面,在里面加上提示性文字或注意事项。
- 如何写这样的界面?如何实现点击不同的菜单栏就跳转到不同的页面?
页面布局使用了 DockPanel,这是一种可以使元素停靠在页面特定方向的布局。此处先定义了顶部和底部的元素(即菜单和状态栏),然后再定义 Frame,这样 Frame 就会位于中间。Frame 的作用是嵌入某个 Page 到 Window 中,此处将显示欢迎信息和注意事项的 WelcomePage 放在了主界面中,并禁用了导航栏。当点击菜单项时,Frame 可以显示其他 Page 的内容。简化后的页面元素如下所示:
<Viewbox>
<!--通过Viewbox可以实现缩放页面时其中的元素也跟着缩放的效果,用于屏幕适配-->
<Canvas Height="1000"
Width="1920">
<DockPanel Background="#F9F9F9"
LastChildFill="False"
Height="1000"
Width="1920">
<Menu DockPanel.Dock="Top">
<!--给四个菜单项添加监听事件-->
<MenuItem Header="人事管理"
Click="RenShi_Click"/>
<MenuItem Header="用户管理"
Click="YongHu_Click"/>
<MenuItem Header="数据备份"
Click="BeiFen_Click"/>
<MenuItem Header="帮助"
Click="BangZhu_Click"/>
</Menu>
<StatusBar DockPanel.Dock="Bottom"/>
<Frame x:Name="mainframe"
Source="WelcomePage.xaml"
NavigationUIVisibility="Hidden"></Frame>
</DockPanel>
</Canvas>
</Viewbox>
在 .cs 文件中,以人事管理菜单项的点击事件为例,通过public static Page1 p1;
定义静态变量 p1,其中 Page1 是另一个新建的 Page 如下所示:
private void RenShi_Click(object sender, RoutedEventArgs e)
{
if (p1 == null)
{
p1 = new Page1(page1_para);
}
//通过Navigate方法可以让Frame显示p1的内容,从而实现跳转
mainframe.Navigate(p1);
}
这样就可以点击实现菜单栏跳转的功能了。
OK,这篇就写到这里,下一篇会阐述不同页面之间的关系,以及每个页面中比较难实现的部分,下次见~~~