3

I am stuck at a problem where i really didn't expect to get held up so many hours and it's driving me nuts

As mentioned in the title i need two DropDownLists where the ToYears List has values starting with the selection of FromYear:

FromYear
2000
2001
2002
2003
2004

ToYear
2002
2003
2004

So i tried to use 2 <asp:dropdownlist> and change the selected ToYear during the SelectedIndexEvent of FromYear but this was triggering the selected FromYear event and somehow it wouldn't fire again.

Now i found the CascadingDropDown from the Ajax Control Toolkit and thought this might be a good thing. But i don't want to call a web service, instead i would like to use a Method in the code behind the actual page.

Also the selection should be remembered after a postback - and the range of years changes depending on properties in the Code behind.

I read somewhere that autopostback does not work with the CascadingDropDown.

What would you think would be the most elegant and easy solution?

Thank you very much in advance.

EDIT: i am going to post a few parts of my post - hope that helps Markup:

    <asp:DropDownList AutoPostBack="True" ID="DropDownFromYear" runat="server"     OnSelectedIndexChanged="FromYearChanged" />
    <asp:Label ID="UntilLabel" runat="server" Text=" until " />
    <asp:UpdatePanel ID="ToYearUpdatePanel" runat="server" style="display: inline-block;">
        <ContentTemplate>
            <asp:DropDownList AutoPostBack="true" ID="DropDownToYear" runat="server" OnSelectedIndexChanged="ToYearChanged" />
        </ContentTemplate>
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="DropDownFromYear" EventName="SelectedIndexChanged" />
        </Triggers>
    </asp:UpdatePanel>

CodeBehind (called from OnInit):

    private void InitializeDropDownYears()
    {

        //Calculate the YearMin YearMax Properties
        CalculateYearMinMax();

        int adaptedFromYear = 0, adaptedToYear = 0;

        //get the previously selected Years
        if (DropDownToYear.SelectedItem != null) adaptedToYear = int.Parse(DropDownToYear.SelectedValue);
        if (DropDownFromYear.SelectedItem != null) adaptedFromYear = int.Parse(DropDownFromYear.SelectedValue);

        //check the minimum year constraints 2005 was selected but minYear is 2010 -> adpated is set to 2010
        if (YearMin > adaptedFromYear || adaptedFromYear == 0) adaptedFromYear = YearMin;
        if (YearMax < adaptedToYear || adaptedToYear == 0) adaptedToYear = YearMax;

        //check the 5 year range constraint
        if ((YearMax - YearMin) > 5)
        {
            adaptedFromYear = DateTime.Now.Year - 2;
            adaptedToYear = DateTime.Now.Year + 2;
        }

        Dictionary<string, string> toYears = new Dictionary<string, string>();
        Dictionary<string, string> fromYears = new Dictionary<string, string>();

        for (int tempYear = YearMin; tempYear <= YearMax; tempYear++)
        {
            fromYears.Add(tempYear.ToString(), tempYear.ToString());
            if (tempYear >= adaptedFromYear)
            {
                toYears.Add(tempYear.ToString(), tempYear.ToString());
            }
        }

        DropDownFromYear.DataSource = fromYears;
        DropDownFromYear.DataValueField = "Key";
        DropDownFromYear.DataTextField = "Value";
        DropDownFromYear.SelectedValue = adaptedFromYear.ToString();
        DropDownFromYear.DataBind();

        DropDownToYear.DataSource = toYears;
        DropDownToYear.DataValueField = "Key";
        DropDownToYear.DataTextField = "Value";
        DropDownToYear.SelectedValue = adaptedToYear.ToString();
        DropDownToYear.DataBind();
        if(!IsPostBack)
        {
            SelectedFromYear = adaptedFromYear;
            SelectedToYear = adaptedToYear;
        }
    }

    private void CalculateYearMinMax()
    {
        IList<Task> taskList = CurrentLicense.TaskList;

        List<DateTime> startDates = taskList.Select(task => task.StartDate).ToList();
        YearMin = startDates.Min(date => date).Year;

        List<DateTime> endDates = taskList.Select(task => task.EndDate).ToList();
        YearMax = endDates.Max(date => date).Year;
    }

EventHandler:

    protected void FromYearChanged(object sender, EventArgs e)
    {
        SelectedFromYear = int.Parse(DropDownToYear.SelectedValue);
        SelectedToYear = int.Parse(DropDownFromYear.SelectedValue);
        if (SelectedFromYear > SelectedToYear)
        {
            SelectedToYear = SelectedFromYear;
        }
        UpdateGanttTables();
    }

    protected void ToYearChanged(object sender, EventArgs e)
    {
        SelectedFromYear = int.Parse(DropDownToYear.SelectedValue);
        SelectedToYear = int.Parse(DropDownFromYear.SelectedValue);
        UpdateGanttTables();
    }
pastrami01
  • 341
  • 4
  • 9
  • this should be perfectly possible with just c#..Can you show what is going on in your selectedIndexChanged methods? And basically if i understand correctly, you want to select a value in the FromYear dropdown, and by selecting that value you want the DDL "ToYear" to start at the value selected in FromYear ? – Thousand Oct 01 '12 at 17:45
  • 3
    Personally, if I saw a website postback just to update a dropdown I'd be shaking my head a little. – Lee Taylor Oct 01 '12 at 17:51
  • Hi Thousand, thanks for the fast response. Actually i could have in the FromYear Dropdown a Range from 1950 to 2050 and if the user selects FromYear:1980 it should show in the ToYear 1980 until 2050. If he previously had a ToYear selected in this Range (e.g. 1990) it should still be selected. If the previously selected Range was smaller it should use the Value of FromYear. Another thing is: if the selected possible Range is bigger than 5 years on first PageLoad DateTime.Now.Year-2 should be selected in the FromYear and for ToYear DateTime.Now.Year+2 should be selected. – pastrami01 Oct 01 '12 at 17:57
  • @LeeTaylor via the dropdown only a part of the page should immediately be refreshed. I intend to use an updatepanel for that. – pastrami01 Oct 01 '12 at 18:01

3 Answers3

1

Filling DropDownList controls might sound easy right? and it is, when you use the default behavior of ASP.NET WebForms. However you could face several problems when you want to get specific functionality, for example, in WebForms, trying to fill DropDownLists using AJAX (this is a real pain, and the only solution I have found is to disable the security check on the page <%@ Page EnableEventValidation="false")

For reference:

How to fill an asp:DropDown client side?

Since you are using WebForms probably the best way is to use the gross UpdatePanel

Example:

Result

enter image description here

ASPX markup

    <asp:ScriptManager runat="server" ID="sm" />
    <asp:UpdateProgress runat="server" AssociatedUpdatePanelID="updatePanel" DisplayAfter="0" DynamicLayout="true">
        <ProgressTemplate>
            Working...
        </ProgressTemplate>
    </asp:UpdateProgress>
    <asp:UpdatePanel runat="server" ID="updatePanel">
        <ContentTemplate>
            <div>
                <asp:Label ID="Label1" Text="From" runat="server" AssociatedControlID="from" />
            </div>
            <div>
                <asp:DropDownList runat="server" ID="from" AutoPostBack="true" CausesValidation="false" OnSelectedIndexChanged="from_SelectedIndexChanged">
                </asp:DropDownList>
            </div>
            <div>
                <asp:Label ID="Label2" Text="To" runat="server" AssociatedControlID="to" />
            </div>
            <div>
                <asp:DropDownList runat="server" ID="to" />
            </div>
        </ContentTemplate>
    </asp:UpdatePanel>

Page code behind

    private const int MaxYear = 2030;
    private const int MinYear = 1959;

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!this.IsPostBack)
        {
            var fromRange = Enumerable.Range(MinYear, MaxYear - MinYear);

            this.from.DataSource = fromRange;
            this.from.DataBind();
        }
    }

    protected void from_SelectedIndexChanged(object sender, EventArgs e)
    {
        var selectedYear = Convert.ToInt32(this.from.SelectedValue);
        var toRange = Enumerable.Range(selectedYear, MaxYear - selectedYear);

        this.to.DataSource = toRange;
        this.to.DataBind();
    }

I just uploaded this sample to my GitHub for reference

Community
  • 1
  • 1
Jupaol
  • 21,107
  • 8
  • 68
  • 100
  • Hi @Jupaol, thank you! looks very promising. But one thing is still not working - and that is the hard part i guess: I have to preselect certain years for the From AND To Dropdown List on first load. If i do that under if(!isPostBack) it wont trigger the selectedIndexChanged Events any more... – pastrami01 Oct 02 '12 at 09:10
  • Do you think that it is possible to use repeaters that are bound to dictionaries with key/value where Key is the year and value is a boolean that states which element is selected? And in code-behind i would simply work with the lists? – pastrami01 Oct 02 '12 at 14:11
  • Hi Jupaol i marked your Reply as Answer although i went with a different approach - but you got me on the right track :) I will edit my initial Post now – pastrami01 Oct 02 '12 at 15:17
0

In the end I figured it out and got - as I think a nice solution because it enables me to:

  • initially fill both DDLs with data
  • preselect items in both DDLs
  • still use EnableEventValidation="true" on the Page
  • i can update the DDLs by simply calling DataBind(); on them

The main difference to my previous approaches is, that I fill the DDLs with data during their OnDataBinding event. So it will happen automatically when i execute DataBind() during PageLoad. Only thing i have to make sure first is to fill the dictionaries that contain my information.

If I want to update ToYearsDDL from the selectionChangeEvent of FromYearsDDL I simply update the Data for ToYearsDDL and call ToYearsDDL.DataBind();

Hope this helps someone else that runs into the same wall!

Here comes the markup:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="LinkedDropDownsBound.aspx.cs"
    Inherits="ASP.Net_Spielwiese.LinkedDropDownsBound"  EnableEventValidation="true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <div>
        <asp:UpdatePanel runat="server" ID="updatePanel">
            <ContentTemplate>
                <div>
                    <asp:Label ID="FromLabel" Text="From" Enabled="false" runat="server"  />
                </div>
                <div>
                    <asp:DropDownList runat="server" ID="FromYearsDDL" AutoPostBack="true" CausesValidation="false" OnDataBinding="DDLFromDataBind"
                        OnSelectedIndexChanged="DDLFromSelectedIndexChanged">
                    </asp:DropDownList>
                </div>
                <div>
                    <asp:Label ID="ToLabel" Text="To" Enabled="false" runat="server" />
                </div>
                <div>
                    <asp:DropDownList runat="server" ID="ToYearsDDL" AutoPostBack="true" CausesValidation="false" OnDataBinding="DDLToDataBind"
                        OnSelectedIndexChanged="DDLToSelectedIndexChanged"/>
                </div>
            </ContentTemplate>
        </asp:UpdatePanel>
    </div>
    </form>
</body>
</html>

And here comes the code behind:

public partial class LinkedDropDownsBound : System.Web.UI.Page
{
    public Dictionary<String, Boolean> FromYears
    {
        get
        {
            if (ViewState["FromYears"] == null)
            {
                ViewState["FromYears"] = new Dictionary<String, Boolean>();
            }
            return ViewState["FromYears"] as Dictionary<String, Boolean>;
        }
        set
        {
            ViewState["FromYears"] = value;
        }
    }
    public Dictionary<String, Boolean> ToYears
    {
        get
        {
            if (ViewState["ToYears"] == null)
            {
                ViewState["ToYears"] = new Dictionary<String, Boolean>();
            }
            return ViewState["ToYears"] as Dictionary<String, Boolean>;
        }
        set
        {
            ViewState["ToYears"] = value;
        }
    }

    public int MinYear = 1975;
    public int MaxYear = 2015;


    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            InitFromYears();
            InitToYears();
            DataBind();
        }
    }

    private void InitFromYears()
    {
        FromYears = new Dictionary<string, bool>();
        IEnumerable<int> fromRange = Enumerable.Range(MinYear, MaxYear - MinYear);

        foreach (var fromYear in fromRange)
        {
            FromYears.Add(fromYear.ToString(), fromYear == (DateTime.Now.Year - 2));
        }
    }

    private void InitToYears()
    {
        ToYears = new Dictionary<string, bool>();
        //get the selected FromYear Value
        int minToYear = Convert.ToInt32(FromYears.FirstOrDefault(dict => dict.Value).Key);
        //make sure ToYears is at least FromYears
        if (minToYear < Convert.ToInt32(FromYears.Min(k => k.Key)))
        {
            minToYear = Convert.ToInt32(FromYears.Min(k => k.Key));
        }
        IEnumerable<int> toRange = Enumerable.Range(minToYear, MaxYear - minToYear);
        foreach (var toYear in toRange)
        {
            ToYears.Add(toYear.ToString(), toYear == (DateTime.Now.Year + 2));
        }
    }

    protected void DDLFromDataBind(object sender, EventArgs e)
    {
        FromYearsDDL.DataSource = FromYears;
        FromYearsDDL.DataValueField = "Key";
        FromYearsDDL.DataTextField = "Key";
        FromYearsDDL.SelectedValue = FromYears.FirstOrDefault(y => y.Value).Key;
    }

    protected void DDLFromSelectedIndexChanged(object sender, EventArgs e)
    {
        //update the FromYear Dictionary
        var tempDictionary = FromYears.ToDictionary(fromYear => fromYear.Key, fromYear => fromYear.Key.Equals(FromYearsDDL.SelectedValue));
        FromYears = tempDictionary;

        //Call Bind on the ToYear DDL
        ToYearsDDL.DataBind();

        //do my other update stuff here
        FromLabel.Text = FromYearsDDL.SelectedValue;
        ToLabel.Text = ToYearsDDL.SelectedValue;
    }

    protected void DDLToSelectedIndexChanged(object sender, EventArgs e)
    {
        //do my other update stuff here
        FromLabel.Text = FromYearsDDL.SelectedValue;
        ToLabel.Text = ToYearsDDL.SelectedValue;
    }

    protected void DDLToDataBind(object sender, EventArgs e)
    {
        InitToYears();
        ToYearsDDL.DataSource = ToYears;
        ToYearsDDL.DataValueField = "Key";
        ToYearsDDL.DataTextField = "Key";
        ToYearsDDL.SelectedValue = ToYears.FirstOrDefault(y => y.Value).Key;
    }
}
pastrami01
  • 341
  • 4
  • 9
0

This is The HTML page

In the code behind c# page

private void BindYearDropdown()
        {
            int year;

            for (year = DateTime.Now.Year; year >= 2010 ; year--)
            {
                DDLYear.Items.Add(year.ToString());
            }
        }

The above code is for Years Back ward

rajesh
  • 115
  • 1
  • 2
  • 8