思考了一个多星期,经过反复测试验证,终于实现了Wpf ComboBox 多级正反向级联,话不多说,下面上代码:
这是一个企业管理系统示例,当然只是一个笑话。数据库有三张表,一张企业表,一张5级行政区划表,全国->省市->地市->县市->乡镇街道,还有一张注册表,标示使用者及其行政区划。为了简化示例,企业表只有2个字段,名称和地址,地址又分为2个字段,一个行政区划Id,联接行政区划表,一个行政区划下的地址。下面使用Ef Code First进行设计表类:
企业表类:
namespace WpfApp.Adress.ComboBox
{
/// <summary>
/// 一般情况下,可以不用继承界面更改通知接口,EF会自动通知,但计算属性并且不保存在数据库的,EF不会通知更改,
/// 同时,既然继承了界面更改通知接口,EF就不会自动通知,需要给每个属性写更改通知。
/// </summary>
public class Company : NotifyPropertyChanged
{
public int CompanyId { get; set; }
private string companyName;
public string CompanyName
{
get => companyName;
set
{
companyName = value;
RaisePropertyChanged("CompanyName");
}
}
private string adress;
public string Adress
{
get => adress;
set
{
if (adress != value)
{
adress = value;
RaisePropertyChanged("Adress");
RaisePropertyChanged("UpAdress");
}
}
}
private int districtId;
public int DistrictId
{
get => districtId;
set
{
if (districtId != value)
{
districtId = value;
RaisePropertyChanged("DistrictId");
RaisePropertyChanged("UpAdress");
}
}
}
public virtual District District { get; set; }
public string UpAdress //只读属性不会作为数据表字段
{
get
{
var upAdrss = "";
District = DataBase.DbContext.Districts.Find(DistrictId);
if (District != null)
{
var d = District;
while (!d.Equals(DataBase.RegData.District))
{
upAdrss = d.DistrictName + upAdrss;
d = d.UpDistrict;
}
}
upAdrss += Adress;
return upAdrss;
}
}
public string FullAdress
{
get
{
if (DataBase.RegData.District.DistrictLevel != 1)
{
return DataBase.RegData.District.DistrictName + UpAdress;
}
return UpAdress;
}
}
}
}
行政区划表类:
namespace WpfApp.Adress.ComboBox
{
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
public class District //行政区域表
{
public District()
{
ChildDistricts = new HashSet<District>();
Companys = new HashSet<Company>();
RegDatas = new HashSet<RegData>();
}
public int DistrictId { get; set; }
[MaxLength(20)]
[Required]
public string DistrictName { get; set; }
public byte DistrictLevel { get; set; }
[ForeignKey("UpDistrict")]
public int UpDistrictId { get; set; }
public virtual ICollection<District> ChildDistricts { get; set; }
public virtual District UpDistrict { get; set; }
public virtual ICollection<Company> Companys { get; set; }
public virtual ICollection<RegData> RegDatas { get; set; }
}
}
注册表类:
namespace WpfApp.Adress.ComboBox
{
using System.ComponentModel.DataAnnotations;
public class RegData
{
public int RegDataId { get; set; }
public int DistrictId { get; set; }
[MaxLength(20)]
[Required]
public string RegName { get; set; }
public virtual District District { get; set; }
}
}
DbCotext类略,这个就标示上面三张表,名称为CompanyDbContext。
企业类有一个DataBase类是这样的:
namespace WpfApp.Adress.ComboBox
{
public class DataBase
{
private static CompanyDbContext dbContext;
public static CompanyDbContext DbContext
{
get
{
if (dbContext == null)
dbContext = new CompanyDbContext();
return dbContext;
}
}
private static RegData regData;
public static RegData RegData
{
get
{
if (regData == null)
{
regData = DbContext.RegDatas.Find(1);
}
return regData;
}
}
public static bool IsFirstRun()
{
var reg = DbContext.RegDatas.Find(1);
return reg == null;
}
/// <summary>
/// 初始化数据库略
/// </summary>
public static void DataInit()
{
//DbContext.Districts.Add(new District() { DistrictName = "全国", DistrictLevel = 1, UpDistrictId = 1 });//1
//DbContext.Districts.Add(new District() { DistrictName = "浙江省", DistrictLevel = 2, UpDistrictId = 1 });//2
//。。。。。。
//DbContext.RegDatas.Add(new RegData() { RegName = "浙江省文化厅", DistrictId = 2 });
//DbContext.SaveChanges();
}
}
}
然后是关键之一,正反向级联的多个ComboBox Wpf代码:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Converters="clr-namespace:WpfApp.Adress.ComboBox.Converters"
x:Class="WpfApp.Adress.ComboBox.AdressDistrict"
mc:Ignorable="d"
d:DesignHeight="25" d:DesignWidth="110">
<UserControl.Resources>
<Converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Horizontal" Visibility="{Binding IsFourthLevel, ConverterParameter=same, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock Text="{Binding District.DistrictName}" VerticalAlignment="Center" Width="Auto"/>
<ComboBox Height="25" Width="Auto" ItemsSource="{Binding District.ChildDistricts}" DisplayMemberPath="DistrictName" SelectedValuePath="DistrictId" SelectedValue="{Binding SelectedCompany.DistrictId, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Visibility="{Binding IsThirdLevel, ConverterParameter=same, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock Text="{Binding District.DistrictName}" VerticalAlignment="Center" Width="Auto"/>
<ComboBox x:Name="comboBox" Height="25" Width="Auto" ItemsSource="{Binding District.ChildDistricts}" DisplayMemberPath="DistrictName" SelectedItem="{Binding ChildDistrict, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}"/>
<ComboBox Height="25" Width="Auto" ItemsSource="{Binding GrandchildDistricts}" DisplayMemberPath="DistrictName" SelectedValuePath="DistrictId" SelectedValue="{Binding SelectedCompany.DistrictId, Mode=TwoWay}" SelectedItem="{Binding SelectedCompany.District, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Visibility="{Binding IsSecondLevel, ConverterParameter=same, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock Text="{Binding District.DistrictName}" VerticalAlignment="Center" Width="Auto"/>
<ComboBox Height="25" Width="Auto" ItemsSource="{Binding District.ChildDistricts}" DisplayMemberPath="DistrictName" SelectedItem="{Binding ChildDistrict, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}"/>
<ComboBox Height="25" Width="Auto" ItemsSource="{Binding ChildDistrict.ChildDistricts}" DisplayMemberPath="DistrictName" SelectedItem="{Binding GrandchildDistrict, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}" />
<ComboBox Height="25" Width="Auto" ItemsSource="{Binding GrandchildDistrict.ChildDistricts}" DisplayMemberPath="DistrictName" SelectedValuePath="DistrictId" SelectedValue="{Binding SelectedCompany.DistrictId, Mode=TwoWay}" SelectedItem="{Binding GreatgrandchildDistrict, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Visibility="{Binding IsFirstLevel, ConverterParameter=same, Converter={StaticResource BooleanToVisibilityConverter}}">
<ComboBox Height="25" Width="Auto" ItemsSource="{Binding District.ChildDistricts}" DisplayMemberPath="DistrictName" SelectedItem="{Binding ChildDistricts, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}"/>
<ComboBox Height="25" Width="Auto" ItemsSource="{Binding ChildDistrict.ChildDistricts}" DisplayMemberPath="DistrictName" SelectedItem="{Binding GrandchildDistrict, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}"/>
<ComboBox Height="25" Width="Auto" ItemsSource="{Binding GrandchildDistrict.ChildDistricts}" DisplayMemberPath="DistrictName" SelectedItem="{Binding GreatgrandchildDistrict, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}" />
<ComboBox Height="25" Width="Auto" ItemsSource="{Binding GreatgrandchildDistrict.ChildDistricts}" DisplayMemberPath="DistrictName" SelectedValuePath="DistrictId" SelectedValue="{Binding SelectedCompany.DistrictId, Mode=TwoWay}" SelectedItem="{Binding SelectedCompany.District, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}"/>
</StackPanel>
</Grid>
</UserControl>
上面代码是根据不同注册者的行政区划级别来选择显示,当然只有县市级及以上部门才能成为注册者。
关键之一的操作类ViewModels:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity.Migrations;
using System.Linq;
namespace WpfApp.Adress.ComboBox
{
/// <summary>
/// 企业录入编辑
/// </summary>
public class CompanyEditViewModel : ViewModelBase
{
/// <summary>
/// Initializes a new instance of the CompanyEditViewModel class.
/// </summary>
#region 构造函数
public CompanyEditViewModel()
{
EditType = EditModeType.Default;
district = DataBase.RegData?.District;
userDistrictLevel = district.DistrictLevel;
SelectedIndex = -1;
}
#endregion
#region 私有字段
private int selectedIndex;
private int oldSelectedIndex;
private Company selectedCompany;
private Company pocodCompany = new Company();
private CompanyDbContext DbContext = DataBase.DbContext;
private bool IsDefaultMode
{
get { return EditType == EditModeType.Default; }
}
private bool IsSelected
{
get { return (SelectedCompany != null); }
}
private ObservableCollection<Company> companys;
enum EditModeType
{
Default, //浏览(非编辑)模式
Add, //添加模式
Edit //修改模式
}
private EditModeType editType;
private EditModeType EditType
{
get { return editType; }
set
{
editType = value;
RaisePropertyChanged("IsEnabled");
RaisePropertyChanged("IsReadOnly");
}
}
byte userDistrictLevel;
District district;
District childDistrict;
District grandchildDistrict;
District greatgrandchildDistrict;
#endregion
#region 绑定字段
public District District
{
get
{
return district;
}
}
public bool IsFirstLevel
{
get
{
return userDistrictLevel == 1;//部委
}
}
public bool IsSecondLevel
{
get
{
return userDistrictLevel == 2;//省市
}
}
public bool IsThirdLevel
{
get
{
return userDistrictLevel == 3;//地市
}
}
public bool IsFourthLevel
{
get
{
return userDistrictLevel == 4;//县市
}
}
public District ChildDistrict
{
get
{
return childDistrict;
}
set
{
childDistrict = value;
RaisePropertyChanged("ChildDistrict");
RaisePropertyChanged("GrandchildDistricts");
}
}
public District GrandchildDistrict
{
get
{
return grandchildDistrict;
}
set
{
grandchildDistrict = value;
RaisePropertyChanged("GrandchildDistrict");
RaisePropertyChanged("GreatgrandchildDistricts");
}
}
public District GreatgrandchildDistrict
{
get
{
return greatgrandchildDistrict;
}
set
{
greatgrandchildDistrict = value;
RaisePropertyChanged("GreatgrandchildDistrict");
}
}
public IList<District> GrandchildDistricts
{
get
{
return ChildDistrict?.ChildDistricts.ToList();
}
}
public IList<District> GreatgrandchildDistricts
{
get
{
return GrandchildDistrict?.ChildDistricts.ToList();
}
}
public int SelectedIndex
{
get { return selectedIndex; }
set
{
if (selectedIndex != value)
{
selectedIndex = value;
RaisePropertyChanged("SelectedIndex");
}
}
}
public Company SelectedCompany
{
get
{
return selectedCompany;
}
set
{
if (selectedCompany != value)
{
selectedCompany = value;
SetCurrentDistrict();
RaisePropertyChanged("SelectedCompany");
RaisePropertyChanged("isSelected");
}
}
}
public ObservableCollection<Company> Companys
{
get
{
if (companys == null)
{
companys = new ObservableCollection<Company>(DbContext.Companys);
}
return companys;
}
}
public bool IsEnabled
{
get { return !IsDefaultMode; }
}
public bool IsReadOnly
{
get { return IsDefaultMode; }
}
#endregion
#region 私有方法
private void SetCurrentDistrict()//实现反向级联
{
switch (userDistrictLevel)
{
case 1:
ChildDistrict = SelectedCompany?.District?.UpDistrict.UpDistrict.UpDistrict;
GrandchildDistrict = SelectedCompany?.District?.UpDistrict.UpDistrict;
GreatgrandchildDistrict = SelectedCompany?.District?.UpDistrict;
break;
case 2:
ChildDistrict = SelectedCompany?.District?.UpDistrict.UpDistrict;
GrandchildDistrict = SelectedCompany?.District?.UpDistrict;
break;
case 3:
ChildDistrict = SelectedCompany?.District?.UpDistrict;
break;
case 4:
break;
}
}
internal override void Add()
{
var newCompany = new Company();
AddNew(newCompany);
EditType = EditModeType.Add;
}
private void AddNew(Company p)
{
companys.Add(p);
oldSelectedIndex = SelectedIndex;
SelectedIndex = Companys.IndexOf(p);
SelectedCompany = p;
}
internal override bool CanAdd()
{
return IsDefaultMode;
}
internal override void Edit()
{
BackUpItem();
EditType = EditModeType.Edit;
}
private void BackUpItem()//保存原始数据
{
pocodCompany.CompanyId = SelectedCompany.CompanyId;
pocodCompany.CompanyName = SelectedCompany.CompanyName;
pocodCompany.DistrictId = SelectedCompany.DistrictId;
pocodCompany.Adress = SelectedCompany.Adress;
}
internal override bool CanEdit()
{
return (IsDefaultMode && IsSelected);
}
internal override void Cancel()
{
if (EditType == EditModeType.Add)
{
Companys.Remove(SelectedCompany);
SelectedIndex = oldSelectedIndex;
}
else
{
BackToItem();//返回原始数据
}
EditType = EditModeType.Default;
}
private void BackToItem()
{
if (!SelectedCompany.Equals(pocodCompany))
{
SelectedCompany.CompanyName = pocodCompany.CompanyName;
SelectedCompany.DistrictId = pocodCompany.DistrictId;
SelectedCompany.Adress = pocodCompany.Adress;
}
}
internal override bool CanCancel()
{
return !IsDefaultMode;
}
internal override void Save()
{
DbContext.Companys.AddOrUpdate(SelectedCompany);
DbContext.SaveChanges();
EditType = EditModeType.Default;
}
internal override bool CanSave()
{
return !IsDefaultMode;
}
internal override void Delete()
{
DbContext.Companys.Remove(SelectedCompany);
DbContext.SaveChanges();
Companys.Remove(SelectedCompany);
}
internal override bool CanDelete()
{ return (IsDefaultMode && IsSelected); }
internal override bool CanExit() { return IsDefaultMode; }
internal override void Exit()
{
DbContext.Dispose();
base.Exit();
}
#endregion
}
}
其他的辅助类就不上代码了。
整个工程文件:
运行界面:
工程代码下载:http://download.youkuaiyun.com/download/luwq168/10195850