65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET AJAX MultiHandleSliderExtender - 按年和月滑动

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (14投票s)

2010 年 3 月 7 日

CPOL

8分钟阅读

viewsIcon

64993

downloadIcon

2103

教程 - 如何利用 ASP.NET MultiHandleSlider 扩展器来选择一个范围内的年份和月份,并设置图表控件使其正常工作。

引言

在本教程中,我将演示如何使用 MultiHandleSlider 扩展器来选择或显示一个范围内的年份和月份。该控件消除了使用四个 DropDownList 控件来保存范围值和验证控件来验证用户选择的需要。然后,我们将使用柱状图根据选定的范围值显示芝麻街汽车的数量。此图表允许用户深入查看所选汽车品牌的详细信息。参见图 1。

图 1

MultiHandleSlider results

入门

  1. 下载 AJAX Control Toolkit 版本说明 - 2009 年 5 月发布
  2. 下载 图表控件示例环境
  3. 下面显示的是项目解决方案资源管理器快照。欢迎下载此演示。
图 2

Project Structure

后端数据库

首先,向表中添加一个新列,命名为 YearMonth,并用年份和月份数据的连接填充它。参见图 3。左侧是原始表。有了这个设置,我们就可以轻松地从表中选择所需年份和月份范围内的所有数据。

图 3

Add column to table

使用 ROW_NUMBER 函数为结果集中的每一行生成行号。然后,我们可以使用行号来填充滑块的范围值。下面显示的查询返回六十二行,这意味着滑块的范围值从一到六十二。换句话说,我们可以将滑块的最小值和最大值属性分别设置为一和六十二。

图 4

Create Row Number

将所有内容整合

为了简化起见,本教程中使用 XML 作为数据源。App_Data 文件夹中有两个 XML 文件,分别是 CarsList.xmlSliderRange.xml。前者包含图 3 中所示表中的所有数据。后者 XML 文件包含图 4 中所示的结果集。创建两个名为 CarsListRange 的自定义实体类来保存公共属性。参见列表 1。

列表 1
public class CarsList
{
    public CarsList(){}
    public int CarCount { get; set; }
    public int YearMonth { get; set; }
    public string CarBrand { get; set; }
    public string Date { get; set; }
}
public class Range
{
    public Range(){}
    public string Year { get; set; }
    public string Month { get; set; }
    public int RowNumber { get; set; }
}

让我们首先在页面上添加一个 ScriptManager,并将 EnablePageMethodsEnablePartialRendering 属性设置为 true。通过将 EnablePageMethods 属性设置为 true,客户端脚本就可以访问 ASP.NET 页面的静态页面方法。EnablePartialRendering 属性允许我们仅指定要刷新的页面区域。接下来,将一个 TextBoxMultiHandleSliderExtender、四个 HiddenField 和两个 Label 控件拖放到页面上,并将它们包装在 UpdatePanel 中。将 MultiHandleSliderExtender 控件的 TargetControlID 设置为 TextBox 控件的 ID。向控件添加两个滑块,并分别将其 ControlID 设置为 rangeStartrangeEnd。参见列表 2。Label 控件的目的是显示选定的范围值。HiddenField 控件用于保存滑块的值。使用如下所示的设置初始化 MultiHandleSliderExtender

  • OnClientDrag= Drag - 当用户拖动滑块时引发的事件。
  • OnClientDragEnd = DragEnd - 当用户停止拖动滑块时引发的事件。
  • Increment = 1 - 确定滑块值递增或递减的点数。
  • RaiseChangeOnlyOnMouseUp = true - 仅当释放鼠标左键时,才在扩展的 TextBox 上触发更改事件。
  • EnableRailClick = false.
列表 2
<asp:ScriptManager ID="ScriptManager1" runat="server" 
          EnablePageMethods="true" EnablePartialRendering="true" /> 
    <div>
    <asp:UpdatePanel ID="UpdatePanel2" runat="server">
<ContentTemplate>
<table>
    <tr><td colspan="2">
       <asp:TextBox ID="txtSlider" runat="server"></asp:TextBox>  
    
    <cc1:MultiHandleSliderExtender ID="MultiHandleSliderExtender1" 
           runat="server" ShowHandleDragStyle="true"
           BehaviorID="mhSlider" TargetControlID="txtSlider" 
           Length="500" ShowInnerRail="true"
           EnableMouseWheel="false" Increment="1" 
           RaiseChangeOnlyOnMouseUp="true" EnableRailClick="false"
           OnClientDragEnd="DragEnd" OnClientDrag="Drag" 
           ShowHandleHoverStyle="true" 
           Maximum="222" Minimum="1">
        <MultiHandleSliderTargets>
         <cc1:MultiHandleSliderTarget ControlID="rangeStart" />
            <cc1:MultiHandleSliderTarget ControlID="rangeEnd" />
        </MultiHandleSliderTargets>  
   </cc1:MultiHandleSliderExtender>
   <br />
   </td></tr>
   <tr>
   <td><asp:Label ID="lblStartRange" runat="server" 
             Text=""></asp:Label></td>
   <td><asp:Label ID="lblEndRange" runat="server" 
             Text=""></asp:Label> </td>
   </tr>  
    <tr><td>
    <asp:HiddenField ID="rangeEnd" Value="10" runat="server" />
    <asp:HiddenField ID="rangeStart" Value="1" runat="server" />
     <asp:HiddenField ID="hdfTrackRangeStart" runat="server" Value="0" />
 <asp:HiddenField ID="hdfTrackRangeEnd" runat="server" Value="0" />
    </td></tr>
</table>
</ContentTemplate>            
</asp:UpdatePanel>
</div>

下面显示的是客户端代码。拖动滑块将触发 ASP.NET 页面方法 SliderRange 来更新 lblStartRangelblEndRange 的值。一旦用户释放滑块,DragEnd 函数将被执行。

列表 3
<script type="text/javascript">
    var isDragging = false;
    function Drag(sender, args) {
        GetSliderRange($get("rangeStart").value, $get("rangeEnd").value);
    }

    function DragEnd(sender, args) {
        //prevent postback on slider click
        if ($get("hdfTrackRangeStart").value !== $get("rangeStart").value) {
            // __doPostBack("btnLoadChart", "");
        }
        if ($get("hdfTrackRangeEnd").value !== $get("rangeEnd").value && 
            $get("hdfTrackRangeEnd").value !== '0') {
            // __doPostBack("btnLoadChart", "");
        }
    }

    function GetSliderRange(startV, endV) {
        PageMethods.SliderRange(startV, endV, this.callback);
    }

    function callback(result) {
        var arrResult = result.split("--");
        $get("lblStartRange").innerHTML = arrResult[0];
        $get("lblEndRange").innerHTML = arrResult[1];
    }
</script>

在代码隐藏中创建一个通用的 List<T> Range 对象,并将其标记为 static,以便客户端脚本或其他静态方法可以访问它。下面显示的是 Page_Load 事件中的内容。PopulateSlider() 方法读取 SliderRange.xml 文件中的内容并将其存储在 lstSliderRange 中。将 MultiHandleSliderExtender 的最小值和最大值分别初始化为 1 和 lstSliderRange 中的最大 RowNumber。参见列表 4。

列表 4
protected static List<range> lstSliderRange = null;
protected void Page_Load(object sender, EventArgs e)
{
    Chart1.Click += new ImageMapEventHandler(Chart1_Click);
    PopulateSlider();

    if (!Page.IsPostBack)
    {
        //slider min and max value
        MultiHandleSliderExtender1.Minimum = 1;
        MultiHandleSliderExtender1.Maximum = 
        int.Parse(lstSliderRange.Max(r => r.RowNumber).ToString());

        //hidden field
        rangeEnd.Value = MultiHandleSliderExtender1.Maximum.ToString();
        rangeStart.Value = MultiHandleSliderExtender1.Minimum.ToString();

        PopulateChart(int.Parse(rangeStart.Value), int.Parse(rangeEnd.Value));
    }
    SetLabel();
}

下面显示的是 PopulateSlider() 方法的实现。lstSliderRange 对象被缓存以提高性能,并在文件内容更改时再次获取其内容。根据数据源更新的频率,我们可以基于数据库、文件夹、文件或基于时间的过期进行缓存。在此处阅读更多关于 ASP.NET 缓存技术的信息。

列表 5
//get slider range
void PopulateSlider()
{
    //Cache the frequently used data
    if (Cache["Cache_lstSliderRange"] == null)
    {
        XDocument xmlDoc = XDocument.Load(Server.MapPath(
                              Utilities.Instance.SliderRangeXMLPath));
        lstSliderRange = (from c in xmlDoc.Descendants("Range")
                          select new Range
                          {
                              Month = (string)c.Attribute("Month"),
                              Year = (string)c.Attribute("Year"),
                              RowNumber = (int)c.Attribute("RowNumber")
                          }).ToList();

        Cache.Insert("Cache_lstSliderRange", lstSliderRange,
            new System.Web.Caching.CacheDependency(
                Server.MapPath(Utilities.Instance.SliderRangeXMLPath)));
    }
    else
    {
        lstSliderRange = Cache["Cache_lstSliderRange"] as List<range>;
    }
}

SetLabel() 方法在 lblStartRangelblStartEnd Label 控件中显示 MultiHandleSliderExtender 的开始和结束范围值。SliderRange 方法带有 [System.Web.Services.WebMethod] 装饰,使其可以通过客户端 JavaScript 访问。GetSliderText 方法接受两个参数:第一个参数是指行号,第二个参数是指左滑块或右滑块。例如,调用 SliderRange(2, 10) 将返回“从年份:2005 月份:02--到年份:2005 月份:10”。首先,它将查询 lstSliderRange 对象并从结果集中检索年份和月份。如果 pos==s,则文本设置为 **From**,如果 pos==e,则设置为 **To**。参见列表 6。

列表 6
//set the slider start and end label
void SetLabel()
{
    string[] arrLabel = SliderRange(rangeStart.Value, 
             rangeEnd.Value).Split("--".ToCharArray());
    lblStartRange.Text = arrLabel[0];
    lblEndRange.Text = arrLabel[2];
}

[System.Web.Services.WebMethod]
public static string SliderRange(string start, string end)
{
    if (lstSliderRange != null)
    {
        return GetSliderText(start, "s") + "--" + 
                             GetSliderText(end, "e");
    }
    else
    {
        return "";
    }
}

protected static string GetSliderText(string rn, string pos)
{
    string strRangeText = string.Empty;
    IEnumerable<range> rangeText;

    rangeText = lstSliderRange.Where(r => r.RowNumber == int.Parse(rn))
        .Select(r => new Range
        {
            Year = r.Year,
            Month = r.Month
        });

    if (pos == "s")
    {
        strRangeText = "From Year: " + rangeText.First().Year + 
                       " Month: " + rangeText.First().Month;
        return strRangeText;
    }
    else
    {
        strRangeText = "To Year: " + rangeText.First().Year + 
                       " Month: " + rangeText.First().Month;
        return strRangeText;
    }
}

此时,您应该在浏览器中看到类似以下的界面。

图 5

Check point

图表控件

请确保下载 Microsoft Chart Controls for Microsoft .NET Framework 3.5,因为图表控件需要 System.Web.DataVisualization.Design.dllSystem.Web.DataVisualization.dll。我也将这两个 DLL 包含在示例代码中。此部分中的一些代码来自 图表控件示例环境。首先,让我们创建一个方法将数据源绑定到柱状图。此方法接受两个参数:开始和结束范围值。然后,使用 LINQ 查询 CarsList.xml 数据源,找到 YearMonth 在开始和结束范围值之间的所有记录。按汽车品牌对结果进行分组,将其存储在 lstCarsnTotal 中,并将其绑定到图表。参见列表 7。

列表 7
void PopulateChart(int start, int end)
{
    List<carslist> lstCarsnTotal = new List<carslist>();

    XDocument xmlDoc = XDocument.Load(Server.MapPath(Utilities.Instance.CarsListXMLPath));
    lstCarsnTotal = (from c in xmlDoc.Descendants("Car")
                     where (int)c.Attribute("YearMonth") >= GetRange(start) && 
                           (int)c.Attribute("YearMonth") <= GetRange(end)
                     group c by (string)c.Attribute("CarBrand") into g

                     select new CarsList
                     {
                         CarCount = g.Sum(c => (int)c.Attribute("Count")),
                         CarBrand = g.Key
                     }).ToList();
    Chart1.Series["Default"].ChartType = SeriesChartType.Column;
    Chart1.Series["Default"].Points.DataBindXY(lstCarsnTotal, "CarBrand", 
                  lstCarsnTotal, "CarCount"); 
}

//return YearMonth
protected static int GetRange(int rn)
{
    IEnumerable<range> rangeText;

    rangeText = lstSliderRange.Where(r => r.RowNumber == rn)
        .Select(r => new Range
        {
            Year = r.Year,
            Month = r.Month
        });

    return int.Parse(rangeText.First().Year + rangeText.First().Month);
}

现在,当用户单击柱状图时,一个 GridView 将出现在其旁边。PopulateGrid 方法以汽车品牌作为参数。然后,使用 LINQ 查询 SliderRange.xml 数据源,其中 YearMonth 在选定范围值之间,并且 CarBrand 等于选定的汽车品牌。参见列表 8。

列表 8
protected void Chart1_Click(object sender, ImageMapEventArgs e)
{
    if (!GridView1.Visible)
    {
        GridView1.Visible = true;
    }
    //kept track of selected car type
    ChartPostBackValue.Value = e.PostBackValue;
    lblCarBrand.Text = "Car Brand: " + e.PostBackValue;

    PopulateGrid(e.PostBackValue);
    PopulateChart(int.Parse(rangeStart.Value), int.Parse(rangeEnd.Value));
}

void PopulateGrid(string strPostBavkVal)
{
    List<carslist> lstCarsnTotal = new List<carslist>();

    XDocument xmlDoc = XDocument.Load(Server.MapPath(Utilities.Instance.CarsListXMLPath));
    lstCarsnTotal = (from c in xmlDoc.Descendants("Car")
                     where (int)c.Attribute("YearMonth") >= 
                                            GetRange(int.Parse(rangeStart.Value))
                     && (int)c.Attribute("YearMonth") <= 
                                            GetRange(int.Parse(rangeEnd.Value)) 
                     && (string)c.Attribute("CarBrand") == strPostBavkVal
                     select new CarsList
                     {
                         CarCount = (int)c.Attribute("Count"),
                         CarBrand = (string)c.Attribute("CarBrand"),
                         Date = (string)c.Attribute("Date")
                     }).ToList();

    GridView1.DataSource = lstCarsnTotal;
    GridView1.DataBind();
}

已知问题

在设计时,我们会看到错误“MultiHandleSliderExtender 无法设置在 MultiHandleSliderTargets 属性上”,但代码在运行时可以正常工作。我下载了示例和最新版本的 AJAX Control Toolkit 从 CodePlex 下载,但未能解决问题。一种变通方法是在设计时在 MultiHandleSliderTargets 标签旁边添加 TagPrefix,并在运行时将其删除。希望有人能对此有所阐明。

关注点

示例代码中的 Default2.aspx 包含一个母版页。如果您使用母版页,请确保使用 Control.ClientID。出于某种原因,客户端 __doPostBack 函数不能与母版页一起工作;变通方法是调用按钮单击事件。参见列表 9。希望有人也能对此有所阐明。

列表 9
<script type="text/javascript">
    var isDragging = false;
    function Drag(sender, args) {
        GetSliderRange($get("<%= rangeStart.ClientID %>").value, 
                       $get("<%= rangeEnd.ClientID%>").value);
    }
    function DragEnd(sender, args) {
        //prevent postback on slider click
        if ($get("<%= hdfTrackRangeStart.ClientID %>").value !== 
                 $get("<%= rangeStart.ClientID %>").value) {
            $get("<%= btnLoadChart.ClientID %>").click();
            //__doPostBack("<%= btnLoadChart.ClientID %>", "");
        }
        if ($get("<%= hdfTrackRangeEnd.ClientID %>").value !== 
               $get("<%= rangeEnd.ClientID %>").value && 
               $get("<%= hdfTrackRangeEnd.ClientID %>").value !== '0') {
            $get("<%= btnLoadChart.ClientID %>").click();
            //__doPostBack("<%= btnLoadChart.ClientID %>", "");
        }
    }

    function GetSliderRange(startV, endV) {
        PageMethods.SliderRange(startV, endV, this.callback);
    }

    function callback(result) {

        var arrResult = result.split("--");

        $get("<%= lblStartRange.ClientID %>").innerHTML = arrResult[0];
        $get("<%= lblEndRange.ClientID %>").innerHTML = arrResult[1];

    }
</script>

我注意到,单击滑块会触发 DragEnd 函数并导致不必要的发布回退。为了解决这个问题,请比较先前选定的范围值与当前选定的范围值;如果它们不相等,则允许调用客户端 __doPostBack 函数。参见列表 10。

列表 10
function DragEnd(sender, args) {
    //prevent postback on slider click
    if ($get("hdfTrackRangeStart").value !== $get("rangeStart").value) {
         __doPostBack("btnLoadChart", "");
    }
    if ($get("hdfTrackRangeEnd").value !== 
               $get("rangeEnd").value && 
               $get("hdfTrackRangeEnd").value !== '0') {
         __doPostBack("btnLoadChart", "");
    }
}

图表在我的本地计算机上显示正常,但在托管环境中显示一系列奇怪的字符。经过一些研究,我发现我没有在存储文件夹上设置适当的权限,并且页面指令上的 EnableSessionState 被设置为 false。ASP.NET 图表存储模式的列表可以在这里找到:here

新更新 1

创建一个名为 arrRange 的客户端 Array 对象,循环遍历 lstSliderRange 对象中的每一行,并将年份和月份的值添加到其中。将 CreateArray() 方法放在 !Page.IsPostBack 块内。参见列表 11。

列表 11
void CreateArray()
{
    foreach (Range r in lstSliderRange)
    {
        Page.ClientScript.RegisterArrayDeclaration("arrRange", 
             "'"+r.Year +"--" + r.Month+"'");
    }
}

不要使用 PageMethods 来查找 RowNumber 文本,而是调用客户端 GetSliderText 函数。参见列表 12。

列表 12
function GetSliderRange(startV, endV) {
    $get("<%= lblStartRange.ClientID %>").innerHTML = 
                      GetSliderText(arrRange[startV - 1], 's');
    $get("<%= lblEndRange.ClientID %>").innerHTML = 
                      GetSliderText(arrRange[endV - 1], 'e');
    // alert(arrRange[startV - 1]);
    // PageMethods.SliderRange(startV, endV, this.callback);
}

function GetSliderText(r, p) {
    var arrResult = r.split("--");
    var strText = '';
    if (p === 's') {
        strText = "<b>From</b> Year: " + 
                  arrResult[0] + " Month: " + arrResult[1];

    }
    else {
        strText = "<b>To</b> Year: " + 
                  arrResult[0] + " Month: " + arrResult[1];
    }
    return strText;
}

结论

如果您发现任何错误或不同意内容,请给我留言,我会与您合作进行纠正。我建议下载演示并进行探索,以充分理解其概念,因为我可能遗漏了一些有用的信息。

IE, Firefox, Google Chrome, Safari

资源

历史

  • 2010/03/07 - 首次发布。
  • 2010/03/09 - 将 PageMethods 替换为客户端 JavaScript Array
  • 我收到一位 CodeProject 成员的反馈,他说:“问题是,拖动会向服务器发送许多请求,最后一个会执行,但这同样意味着数据库需要额外工作,IIS 也需要。” 我认为他说得有道理,所以我决定将每个 RowNumber 的年份和月份数据存储在客户端 JavaScript 数组中。这将允许通过客户端更新滑块的范围标签。参见新更新 1 部分。

© . All rights reserved.