首页蓝耳朵|小小蓝耳朵广州图书馆外文室英语儿童读物资源介绍网佛教青年之友旧版收集永硕E盘Phonics Short Vowels Game 
中国省市二级联动和省市县三级联动下拉菜单
所属栏目:ASP.NET(webform)  时间:2010-01-20 13:01  作者:狼子

用的数据库是从中国省市县数据库2009年1月版.mdb(http://download.csdn.net/source/975699)里导出来的,2009年1月版,是因为发布日期是2009-01-18 01:44,这个数据库的数据是什么时候整理的我不知道,我用的数据是从这里导出来的,我按我要的格式导到了MS SQL 2000里

昨天大猫说数据不准确,我从华通数据中心里截了三个页面,还没按这个重新整理过数据:

城市名单:http://data.acmr.com.cn/member/city/city_md.asp
地级市名单:http://data.acmr.com.cn/member/city/city_djmd.asp
县级市名单:http://data.acmr.com.cn/member/city/city_xjmd.asp

这个是我的数据库的两个文件,需要的人可以直接在SQL里附加/uploadfile/2010/01/20/20100120124430671.rar

这个是晚的数据库导出后的txt文件,不用SQL的人随便导哪个自己的数据库里都可以/uploadfile/2010/01/20/20100120124445734.rar

数据库里有两个表,DicArea用于三级联动,DicCity用于二级联动,二级联动我用的是js脚本,从数据库里生成一个js脚本后,在网站里直接用js脚本,三级联动我用的是数据库,直接用Ajax读取数据库

用的是vs.net2005,没有导入非官方Ajax控件,所以全部东西是自己写的,不要和我说建议vs.net2008,我知道在2008里怎么搞这个,就是,已经用2005写好的项目不可以因为一个问题全部升到2008的

全部测试代码:/uploadfile/2010/01/20/20100120130117468.rar

记录网址:

2006-11-15 14:32《使用showModalDialog打开模态窗口添加数据后刷新原窗口》:http://www.nnllok.cn/myBlog/archives/2006/3024.html

2006-12-28 19:13《模板列里出现“DropDownList 有一个无效 SelectedValue”的解决方法》:http://www.nnllok.cn/myBlog/archives/2006/3237.html

2007-01-10 19:24《回发或回调参数无效》:http://www.nnllok.cn/myBlog/archives/2007/3265.html

2007-04-02 20:28《在微软中文社区请教“回发或回调参数无效”》:http://www.nnllok.cn/myBlog/archives/2007/3584.html

2007-04-04 16:49《使用模态窗口返回后修改原窗口的列表控件,解决回发或回调参数无效》:http://www.nnllok.cn/myBlog/archives/2007/3591.html

2008-10-17 16:55《利用“不经过回发而实现客户端回调”解决“回发或回调参数无效”的问题》:http://www.nnllok.cn/myBlog/archives/2008/4646.html

因为DropDownList在用客户端修改过option(ListItem)后,会导致“回发或回调参数无效”的错误,所以一直在用各种办法解决这个问题,甚至用过在服务器端先把所有数据读取出来注册好后,再用js清空添加需要的数据这样子的办法,这个办法好处是代码好简单,坏处是,要在页面一开始时,读取所有数据,如果是三级联动,县的数据有3000多。。。数据量太大了

现在使用的办法是

解决不到就避开

我用了客户端控件select给用户操作,用了HiddenField在select每一次初始化和选项变更时记录当前的选项值,用来和服务器交互,我把服务器控件DropDownList扔掉了

先说三级联动,文件一共四个:

脚本文件:Area.js
      自定义控件:AreaAjax.ascx   (AreaAjax.ascx.cs)
获取市县列表的后台文件:AreaAjaxList.aspx (AreaAjaxList.aspx.cs)
  测试调用自定义控件:TestUoAreaList.aspx(TestUoAreaList.aspx.cs)

先看自定义控件:AreaAjax.ascx,在这个控件里,我要做的是三级联动时,允许数据绑定到县,用于注册用户或者修改用户资料,我还要在搜索时,只搜索到市一级,不显示县级数据

AreaAjax.ascx

<script type="text/javascript" src="/MyUo/Area.js"></script>

<select name="aProvince" onchange="setCityList(this.value, '');" id="aProvince"></select>
<select name="aCity" onchange="setCountyList(this.value, '');" id="aCity"></select>
<asp:Literal ID="LtCountySelect" runat="server" Text="<select name='aCounty' onchange='setCounty(this.value);' id='aCounty'></select>"></asp:Literal>

<asp:HiddenField ID="HfProvince" runat="server" /><asp:HiddenField ID="HfCity" runat="server" /><asp:HiddenField ID="HfCounty" runat="server" />
<asp:HiddenField ID="HfPList" runat="server" /><asp:HiddenField ID="HfCList" runat="server" /><asp:HiddenField ID="HfOlist" runat="server" />
       
<script type="text/javascript">
//三个选项的客户端控件,用于和用户交互
var objProvince = document.getElementById("aProvince");
var objCity = document.getElementById("aCity");
var objCounty = document.getElementById("aCounty");

//三个选项的服务器端控件,用于和服务器交互
var hiProvince = document.getElementById("<% =HfProvince.ClientID %>");
var hiCity = document.getElementById("<% =HfCity.ClientID %>");
var hiCounty = document.getElementById("<% =HfCounty.ClientID %>");

//三个记录当前选项列表的服务器控件,用于避开页面自提交时重复检查数据
var hiPList = document.getElementById("<% =HfPList.ClientID %>");
var hiCList = document.getElementById("<% =HfCList.ClientID %>");
var hiOList = document.getElementById("<% =HfOlist.ClientID %>");
</script>

看红色那一行,这一行是用于显示县的数据,为了实现按需求显示,在这里我用了Literal控件,从服务器端写入select控件,同时控件display属性


AreaAjax.ascx.cs

/// <summary>
    /// 设置是否显示县
    /// </summary>
    public bool ShowCounty
    {
        set
        {
            if (value)
            {
                LtCountySelect.Text = "<select name='aCounty' onchange='setCounty(this.value);' id='aCounty'></select>";
            }
            else
            {
                LtCountySelect.Text = "<select name='aCounty' onchange='setCounty(this.value);' id='aCounty' style='display:none;'></select>";
            }
        }
    }

    /// <summary>
    /// 获取市编号
    /// </summary>
    public string CityNum
    {
        get { return HfCity.Value; }
    }

    /// <summary>
    /// 地区编号,对应县的编号
    /// </summary>
    public string AreaNum
    {
        get { return HfCounty.Value; }
        set
        {
            HfProvince.Value = value.Substring(0, 2);
            HfCity.Value = value.Substring(0, 4);
            HfCounty.Value = value;
        }
    }
    protected void Page_Load(object sender, EventArgs e)
    {
       
    }

这个控件做的很简单的,就是用了三组控件:

一组三个select控件用于给用户操作选择

一组三个HiddenField控件用户记录用户每一次的选项,这样可以在服务器端直接读取控件的值

最后三个HiddenField控件用于记录每一次从数据库里检索出来的数据列表,在客户端用js修改过的所有数据,在页面提交后(点了搜索按钮、由自提交的控件触发等)都会消失的,每一次页面重新Load,都要重新设置客户端控件,就是,我不想重复读取数据库,所以我把列表记录下来

所有控件都要在客户端由脚本操作,所以我分别又建了9个变量,用document.getElementById获取他们对应的控件,这9个变量相当于全局变量,可以在js脚本里直接调用


下面看脚本文件Area.js

//初始化省
function setProvince()
{
    if(hiPList.value == "")
    {
        //检索省,并记录显示
        var pList = GetList(1, "");
        hiPList.value = pList;
        addOption(objProvince, hiProvince, pList);
        //同时初始化市和县
        setCityList(objProvince.value);
    }
    else
    {
        //由于页面自提交引起的select控件数据丢失,重写,但不重新读取数据库
        addOption(objProvince, hiProvince, hiPList.value);
        addOption(objCity, hiCity, hiCList.value);
        addOption(objCounty, hiCounty, hiOList.value);
    }
}

//根据省初始化市:所属省
function setCityList(province)
{
    //记录省
    hiProvince.value = province;
    //检索市
    var cList = GetList(2, province);
    hiCList.value = cList;
    addOption(objCity, hiCity, cList);
    //重新检索县
    setCountyList(objCity.value);
}

//根据市初始化县:所属市
function setCountyList(city)
{
    //记录市
    hiCity.value = city;
    //检索县
    var oList = GetList(3, city);
    hiOList.value = oList;
    addOption(objCounty, hiCounty, oList);
}

//记录县
function setCounty(couty)
{
    hiCounty.value = couty;
}

//获取所有省的列表:列表类别(1省,2市,3县)、父级编号(省为"",市为省,县为市)
function GetList(listType, parentNum)
{
    //参数
    var submitPage, parmAndValue, list;
    submitPage = "/MyUo/AreaAjaxList.aspx";
    parmAndValue = "listType=" + listType + "&parentNum=" + parentNum;
   
    //提交
    list = submitData(submitPage, parmAndValue);
   
    //返回结果
    if(list.indexOf("<") == 0) list = "";
    return list;
}

addLoadEvent(setProvince);


//给控件加入选项:要修改选项的select控件、记录这个select控件的默认选项值的控件、所有option的列表,格式:北京市|11,天津市|22
function addOption(objSelect, objValue, optionList)
{
    var len, index, value;
    var optionArray = new Array();
    var oneOption = new Array();
   
    optionArray = optionList.split(',');
    len = optionArray.length;
   
    //获取默认值
    index = -1;
    value = objValue.value;
   
    //清空所有选项
    objSelect.options.length = 0;
   
    //把列表加为选项
    for(var i = 0; i < len; i++)
    {
        oneOption = optionArray[i].split('|');
        if(value == oneOption[1]) index = i;
        objSelect.options[i] = new Option(oneOption[0], oneOption[1]);
    }
   
    //设置默认值
    if(index == -1) index = 0;
    objSelect.options[index].selected = true;
    //记录默认值
    objValue.value = objSelect.options[index].value;
}

//提交数据:参数、地址
function submitData(submitPage, parmAndValue)
{  
    //使用表单请求页面
    request = new createXMLHttpRequest();
    request.open("POST", submitPage, false);
    request.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
    request.send(parmAndValue);
   
    //取返回的字条串返回
    var strResult = request.responseText;
    return strResult;
}

//创建XMLHttpRequest对象
function createXMLHttpRequest() //创建XMLHttpRequest对象
{
    if (window.ActiveXObject)   // IE下创建XMLHTTPREQUEST
    {
        xhr = new ActiveXObject("Microsoft.XMLHTTP");
    }
    else if (window.XMLHttpRequest)   // 其他浏览器创建XMLHTTPREQUEST
    {
        xhr = new XMLHttpRequest();
    }
    return xhr;
}

//给页面添加load方法
function addLoadEvent(func)
{
    var oldonload = window.onload;
    if(typeof window.onload != 'function')
    {
        window.onload = func;
    }
    else
    {
        window.onload = function()
        {
            oldonload();
            func();
        }
    }
}

最后蓝色的这三个方法是通用方法,这里我只需要提交数字型数据,所以不需要在submitData对数据加密,如果要提交中文数据,关于乱码的问题看这里:http://www.nnllok.cn/Read.aspx?id=401

红色的addOption方法,是用于按我读取出来的数据格式给select加入option的,我的数据格式是:地区描述|地区编号,地区描述|地区编号,……,比如:北京市|11,天津市|22

在这个方法里,我先用split方法按“,”分割,把所有选项加入optionArray数组,然后清空select控件后,循环这个数组,按“|”分割,把每一个选项的值和文件记录在oneOption数组里,把这个选项添加到select中,最后把select的默认选项记录在一个hidden里

最开头的三个方法,就是用于设置省、市、县三个select的了,我在页面onload时,执行setProvince方法,初始化省,因为是联动的,所以在这个方法的最后,我调用setCityList重设了市,在setCityList方法里,设置完市后,我又调用了setCountyList重设了县,这就是联动啊,对不对?一个带着一个跑的,像接力跑,跑完省了,棒棒给了市,轮到市的跑,市跑完了,棒棒给了县,轮到县的跑,县跑完了,直接记下自己的值就可以了,就像全部接力跑完了,跑到领奖台上拿个牌牌就行了啊

绿色的GetList方法,是用于从服务器获取数据的,省市县三组数据都用这个方法获取,所以我要传递不同的参数,listType是列表类别,省是1,市是2,县是3,parentNum是父级编号,省没有父级,所以是"",市的父级是省所以是省的编号,县的父级是市所以是市的编号

数据库中,DicArea.AreaNum是varchar(6),这个字段的前两个字符是省编号,前四个字符是市编号,全部6个字符是市编号


最后就是从后台获取数据的页面了,AreaAjaxList.aspx

        <asp:SqlDataSource ID="SdsProvince" runat="server" ConnectionString="<%$ ConnectionStrings:loveString %>" SelectCommand="area_province" SelectCommandType="StoredProcedure"></asp:SqlDataSource>
        <asp:SqlDataSource ID="SdsCity" runat="server" ConnectionString="<%$ ConnectionStrings:loveString %>" SelectCommand="area_city" SelectCommandType="StoredProcedure">
            <SelectParameters>
                <asp:Parameter Name="ProvinceNum" Type="String" />
            </SelectParameters>
        </asp:SqlDataSource>
        <asp:SqlDataSource ID="SdsCounty" runat="server" ConnectionString="<%$ ConnectionStrings:loveString %>" SelectCommand="area_county" SelectCommandType="StoredProcedure">
            <SelectParameters>
                <asp:Parameter Name="CityNum" Type="String" />
            </SelectParameters>
        </asp:SqlDataSource>

AreaAjaxList.aspx.cs

protected void Page_Load(object sender, EventArgs e)
    {
        //列表类别(1省,2市,3县)、父级编号(省为"",市为省,县为市)listType=" + listType + "&parentNum
        int listType;
        string parentNum, tName = null, vName = null;
        listType = Convert.ToInt32(Request.Form["listType"]);
        parentNum = Request.Form["parentNum"];

        //Response.Write("\n" + listType.ToString() + "," + parentNum);

        bool ok = true;
        DataView dv = new DataView();
        try
        {
            switch (listType)
            {
                case 1:
                    dv = (DataView)SdsProvince.Select(new DataSourceSelectArguments());
                    tName = "Province";
                    vName = "AreaOrder";
                    break;
                case 2:
                    //Response.Write(parentNum);
                    SdsCity.SelectParameters["ProvinceNum"].DefaultValue = parentNum;
                    dv = (DataView)SdsCity.Select(new DataSourceSelectArguments());
                    tName = "City";
                    vName = "AreaOrder";
                    break;
                case 3:
                    SdsCounty.SelectParameters["CityNum"].DefaultValue = parentNum;
                    dv = (DataView)SdsCounty.Select(new DataSourceSelectArguments());
                    tName = "County";
                    vName = "AreaNum";
                    break;
            }
        }
        catch (System.Data.SqlClient.SqlException)
        {
            //Response.Write(ex.Message);
            ok = false;
        }
        if (dv == null || dv.Table == null || dv.Table.Rows.Count == 0)
        {
            //Response.Write("null");
            ok = false;
        }
        if (ok)
        {
            bool begin = true;
            System.Text.StringBuilder sb = new System.Text.StringBuilder("");
            foreach (DataRow dr in dv.Table.Rows)
            {
                if (begin)
                {
                    begin = false;
                    sb.Append(dr[tName].ToString() + "|" + dr[vName].ToString());
                }
                else
                {
                    sb.Append("," + dr[tName].ToString() + "|" + dr[vName].ToString());
                }
            }
            Response.Write(sb.ToString());
        }
        else
        {  
            Response.Write("");
        }
        dv = null;
        Response.End();
    }

这个页面如果我不用数据源控件,直接自己写代码,会简单好多的,一个SqlConnetion、一个SqlCommand、一个DataTable就可以解决问题了。。。就是很难搞清用数据源控件和自己写代码哪里好哪里不好,如果代码少就是好,那为什么vs.net会越出越大,越出越复杂,自带官方控件越来越多呢?

vs.net2008的新控件ListView,他把DataList、FormView、GridView三个的东西集成到一个里了,有好多人说他慢,说他浪费资源,就是也有好多人在用,是因为它可以自己写代码,也可以自动分页和控制数据,我总觉得,如果程序员写程序简单了,如果不是用资源交换的,就是用用户友好度交换的。。。用户的操作简单一步,程序员的工作就会多一堆,如果你不管资源占用,只管完成任务,代码也会少好多好多

对于这个,数据源控件和自己写代码,我不知道哪一种方式更好,在资源占用方面,谁更好,我只是把数据库的连接工作扔级了数据源控件,这里的代码复杂吗?我不知道。。。

我是想,如果我的注册页面,没有用数据控件FormView做,如果我自己添加一堆服务器控件,再加一个注册按钮,在注册的时候,我自己写代码连接数据库,使用事务来注册,保证在检查完用户名有效性后,不间断操作的继续insert数据,这样子我的注册页面就没有bug了,对不对?我只在客户端用Ajax检查了用户重复性,我连在服务器端提交数据后都忘记重新检查了,这是一个bug,好大的bug,注册会员就算不用事务,我也不应该在insert前不对用户名做检查的

现在,因为我没有这样子做,所以大猫开了两个页面,同时注册相同用户名后,我的程序没有判断出来,两个用户都注册成功了,所以我的注册页面有bug,所以大猫说我的代码写得太复杂,可读性太低,所以大猫把我写的全部推翻掉后自己重新写一个。。。。。。。

就是,为什么我们不要在MS SQL数据库里,给用户表的用户名字段加一个索引,或者一个约束呢?为什么不充分利用数据库本来的功能,要自己在代码里对一个insert语句,也使用事务呢?我不懂,我不明白,我不服气,我好难过。。。

大猫第三次这样子做,第一次是一个asp网站,大猫没有催我,我写程序,大猫从来不催我的,那时候,我是给过滤html标签的问题卡住了,我一直搞一直搞,我以为我有好多时间的,等我搞完,天已经黑了,就是,大猫告诉我,他已经自己完好了,不需要我写的东西了。。。大猫没有写过滤html标签的问题,大猫跳过这个问题了,就是,大猫交货了,我写的没有用,写了的东西没有用

还有最近的一个windows服务,一个让多个服务器同时运行,按规定的时间间隔从网络获取数据包后,操作数据库的服务,我写的代码,是一个重要的地方一个try,我只对有可能发生的东西try,我拒绝使用catch(Exception),因为Exception类包含太多东西了,如果程序执行的时候要对每一个东西都检查一下去找对应的异常,这样子太浪费资源了,就是,我把我考虑到的所有异常都加进去了,就是,我的服务还是一直时不时的因为数据库连接返回null值就停止。。。这是一个bug,我一直在修改这个问题,修改了好多次,每一步重要的地方,我都try。。。最后大猫自己重新写,他说我写的太复杂了,他把我的所有try和catch扔掉,他对整个程序使用try,直接catch(Exception),这样子就是说,大猫是让程序的每一个语句,包括定义变量、赋值变量、变量自加之类的,在执行的时候,都要对所有异常检查一遍,长长长长的操作,这个不是在浪费资源吗?我觉得这样子不是最好的办法,找到没有检查出来的异常才是对的,就是,大猫说现在占用的资源不多,大猫说要的是没有bug,要稳定

昨天是第三次,大猫重写我的代码,因为我的注册页面写的太复杂,一堆文件。。。还有最重要的,我有bug,大猫说我这个页面和那个windows服务一样子,修改了不下十次。。。我没有因为bug修改这么多次,我没有,有的修改是因为要求改变才修改的。。。就是到最后,我还是有bug,因为有bug。。。我在把原来自提交检查用户登录名改成用Ajax检查登录名时,把后台检查用户名重复性当成自提交检查的代码删除了,留下了自提交的方法,删掉了后台insert前检查重复性的代码。。。

因为我的代码复杂,很难读得懂,因为我的代码有bug,所以大猫自己重新写注册页面,不要我写的了

以前,asp的时候,我在开始写东西的时候,我总是要先想安全问题,用多少代码我都要保证代码安全,以前,在大家还用电话线上网的时候,我总是想要网站小,占用资源少,有时候为了减小网站,我会用js和css代替图片的特效,就是,现在已经到处都是ADSL了,大家都在用着2M、3M、4M,在用着光纤,大家的机器都已经不是以前的2.5G、6G硬盘,也不是32M、64M、128M内存了,现在的机器硬盘用T算,固体硬盘更加快,现在的内存没有4G别人说太小,现在的CPU都要用双核。。。

是不是,我不应该继续这样子天天看住资源呢?

我记得小狮,小狮是很好的老师,我一直记得他,在PB家园里,他教了我们好多东西,他也是很看重资源占用的,我想小狮肯定也是在没有bug的情况下看重资源的。。。是我把东西搞反了,我先看资源。。。

一个程序员,如果不可以写出稳定的程序,谁还会用你的程序呢?要稳定,要没有bug,因为我做不到这两点,所以大猫自己重新写,也不要我写的,就是,为什么不可以给我把我误删除错的那个代码补回去呢?为什么要自己写,不给我最后一次修改的机会呢?妈妈说大猫是害怕我修改后又有新的bug。。。

为什么会这样子?为什么我要在把自提交验证改成Ajax客户端验证的时候删错代码?我写的东西都没用了。。。我是全世界最没用的人,写什么,bug什么

三级联动的自定义控件就是这么多了,TestUoAreaList.aspx是测试页面来的

二级联动没什么好说的了,就是两个js脚本City.js和CityList.js,调用的页面是city.html,生成CityList.js的文件是CreateCityListJs.aspx全部都在测试包里了

--------------------------------------------------------------

2010-01-21

时光叔叔说:

程序不能有bug,再高的效率也不如一个bug影响大。稳定性和正确性才是最重要的,远远高于效率和技术的先进性。

程序最重要的是稳定和正确,其次要有良好的交互界面和简单的结构,效率在绝大多数情况下不重要,除非是个别场合。

代码复杂会带来效率的提高,但更会带来出错几率的增加,所以一般都是牺牲效率来换取可靠性。

爸爸说:

如果我错了,我不可以等着别人帮我收拾的,工作的时候没有人会像妈妈一样子跟在后面帮我收拾东西,我要自己把错的找出来,自己先修改正确,不可以把自己的责任推给别人,要自己为自己写的程序负责

如果一件新衣服买回来,要改好多地方我才能穿,妈妈宁愿给我重新买一件新的,如果我的程序出好多bug,改了一个bug又出另一个,大猫就宁愿重新自己写了

太小我穿不进的衣服,妈妈肯定不会给我买的,大猫给我工资叫我写程序,我写完了,他用不到,还要自己重新写,大猫是给钱我写他用不到的东西,他买了自己用不到的程序

如果我想要自己的程序有人用,我就要先保证自己的程序不出错,要保证程序不出错,就要用最简单的办法完成任务,使用的方法越复杂,就越容易出错,在开始写代码的时候,要先想好这样子写程序可以不可以稳定运行

一定要记住:程序不能有bug,效率再高不如一个bug影响大,稳定性和正确性是最重要的,远远高于效率和技术的先进性

我说:

大猫好可怜,都是我搞的。。。还要给我叫大坏蛋,为什么大猫不叫回我大坏蛋呢?我想要写好的程序有人用,我不想要别人扔掉我的程序,我要写没有bug的程序,我要在大猫从我的程序里找出bug前,自己先找出来,修改掉
小楼宝宝的涂鸦花花(Imitater)的博客起名称骨测字皖ICP备06000023号-17