0

I used a treeview component to implement my html editor. I used child nodes to record history events with a data time string including A.C. and B.C. time. Example child nodes of a treeview that I had tested is showing as below.

-History
  ﹂ 0123/05/05 B.C. event001
  ﹂ 2015/01/01 A.D. event002
  ﹂ 2017/01/21 A.D. event003
  ﹂ 2125/12/05 B.C. event000

the correct result after sorting should looks like as below

-History
  ﹂ 2125/12/05 B.C. event000 
  ﹂ 0123/05/05 B.C. event001
  ﹂ 2015/01/01 A.D. event002
  ﹂ 2017/01/21 A.D. event003

I used the code just simillar to the following post: Sorting child nodes of a treeview after populating the treeview in c# winforms

using System.Collections;
using System.Windows.Forms;
namespace TreeViewEditor
{
    public class NodeSorter : IComparer
    {
        public int Compare(object x, object y)
        {
                TreeNode tx = x as TreeNode;
                TreeNode ty = y as TreeNode;
                return string.Compare(tx.Text, ty.Text);
            }
    }
}

It works fine when child nodes content A.D. date time strings only. I would like to know how to sort it correctly when child nodes content both A.D. and B.C. date time string in treeview.

McMillan Cheng
  • 382
  • 1
  • 6
  • 20
  • 1
    What is the data type of the ad/bc date? A short, relevant code snippet would be helpful, showing the sorting that you're currently using (that works for ad) – Rufus L Oct 21 '17 at 19:14
  • 1
    What does your current (non-working) `IComparer` implementation look like? – Mathias R. Jessen Oct 21 '17 at 19:16
  • 3
    If I'm reading correctly, this has less to do with treeview childnodes and more to do with the correct sorting algorithm for a date that can be AD or BC. – Rufus L Oct 21 '17 at 19:16
  • @Rufus L Yes, as I mentioned above, these are only strings in YYYY/MM/DD format that I entered to child nodes . – McMillan Cheng Oct 21 '17 at 19:25
  • @Mathias R. Jessen It just looks like the first child nodes I shown above. – McMillan Cheng Oct 21 '17 at 19:33

2 Answers2

1

One way to do this is to use a class to represent your dates, and implement IComparable on the class. This allows you to customize the sorting algorithm so that B.C. dates are sorted in reverse order (compared to A.D. dates).

Below I've created a class that has properties for the year, month, day, along with a bool that is set to false for B.C. dates. I added a Parse() method that can take one of your example strings (without the "event" part) and return an BCADDate object. It would be trivial to add an "event" property and the ability to parse the event from the string as well, if needed (comment below and I can add it).

Basically the comparison works like:

  1. If the dates do not have the same BC/AD designation, then the BC one comes first
  2. Otherwise, compare the years, then the months, and then the days, and return the result of the comparison of first one that doesn't match. Reverse the result for BC dates by multiplying it by -1
  3. Return 0 if all the properties match

Edit

I added the implementation of the IComparer interface also, in case that helps for using this with the treeview.

Here's the code:

class BCADDate : IComparable, IComparer
{
    public int Year { get; set; }
    public int Month { get; set; }
    public int Day { get; set; }
    public bool IsAD { get; set; }

    public static BCADDate Parse(string input)
    {
        var result = new BCADDate();

        if (string.IsNullOrWhiteSpace(input))
            throw new ArgumentException("input cannot be null, empty, or whitespace");

        var dateADParts = input.Split(' ');
        var dateParts = dateADParts[0].Split('/');

        if (dateParts.Length < 3)
            throw new FormatException(
                "The string must have three parts separated by the '/' character.");

        try
        {
            result.Year = int.Parse(dateParts[0]);
            result.Month = int.Parse(dateParts[1]);
            result.Day = int.Parse(dateParts[2]);
        }
        catch
        {
            throw new FormatException(
                "All parts of the date portion must be valid integers.");
        }

        result.IsAD = (dateADParts.Length == 1)
            ? true  // A.D. is true if nothing is specified
            : dateADParts[1].IndexOf("A", StringComparison.OrdinalIgnoreCase) > -1;

        return result;
    }

    public int CompareTo(object obj)
    {
        var other = obj as BCADDate;
        if (other == null) return 1;

        var BCMultiplier = IsAD ? 1 : -1;

        // Use default comparers for our fields
        if (this.IsAD != other.IsAD)
            return this.IsAD.CompareTo(other.IsAD);
        if (this.Year != other.Year)
            return this.Year.CompareTo(other.Year) * BCMultiplier;
        if (this.Month != other.Month)
            return this.Month.CompareTo(other.Month) * BCMultiplier;
        if (this.Day != other.Day)
            return this.Day.CompareTo(other.Day) * BCMultiplier;

        return 0;
    }

    public int Compare(object x, object y)
    {
        var first = x as BCADDate;
        var second = y as BCADDate;

        if (first == null) return second == null ? 0 : -1;
        return first.CompareTo(second);
    }

    public override string ToString()
    {
        var bcad = IsAD ? "A.D." : "B.C.";
        return $"{Year:0000}/{Month:00}/{Day:00} {bcad}";
    }
}

Here's an example of the usage:

var dates = new List<BCADDate>
{
    BCADDate.Parse("0123/05/05 B.C."),
    BCADDate.Parse("2015/01/01 A.D."),
    BCADDate.Parse("2017/01/21 A.D."),
    BCADDate.Parse("2125/12/05 B.C.")
};

Console.WriteLine("Original ordering:");
dates.ForEach(Console.WriteLine);

dates.Sort();
Console.WriteLine("------------------");

Console.WriteLine("Sorted ordering:");
dates.ForEach(Console.WriteLine);

Console.Write("\nDone!\nPress any key to exit...");
Console.ReadKey();

Output

enter image description here

Rufus L
  • 36,127
  • 5
  • 30
  • 43
  • @ Rufus L Thanks for your quick response, I wish I could vote you up someday.. :D I will integrate your solution in my program and give you feedback when I success. Thank you again. – McMillan Cheng Oct 21 '17 at 23:31
0

Based on Rufus L's solution , I have implemented this sorting function in TreeView component. Amazing, I just make a small modification about NodeSorter of TreeView. Rufus L's solution works just fine in my treeview application.

The modified source codes are showing as below:

using System;
using System.Collections;
using System.Windows.Forms;
namespace TreeViewEditor
{
    class BCADDate : IComparable
    {
        public int Year { get; set; }
        public int Month { get; set; }
        public int Day { get; set; }
        public bool IsAD { get; set; }

        public static BCADDate Parse(string input)
        {
            var result = new BCADDate();

            if (string.IsNullOrWhiteSpace(input))
                throw new ArgumentException("input cannot be null, empty, or whitespace");

            var dateADParts = input.Split(' ');
            var dateParts = dateADParts[0].Split('/');

            if (dateParts.Length < 3)
                throw new FormatException(
                    "The string must have three parts separated by the '/' character.");

            try
            {
                result.Year = int.Parse(dateParts[0]);
                result.Month = int.Parse(dateParts[1]);
                result.Day = int.Parse(dateParts[2]);
            }
            catch
            {
                throw new FormatException(
                    "All parts of the date portion must be valid integers.");
            }

            result.IsAD = (dateADParts.Length == 1)
                ? true  // A.D. is true if nothing is specified
                : dateADParts[1].IndexOf("A", StringComparison.OrdinalIgnoreCase) > -1;

            return result;
        }

        public int CompareTo(object obj)
        {
            var other = obj as BCADDate;
            if (other == null) return 1;

            var BCMultiplier = IsAD ? 1 : -1;

            // Use default comparers for our fields
            if (this.IsAD != other.IsAD)
                return this.IsAD.CompareTo(other.IsAD);
            if (this.Year != other.Year)
                return this.Year.CompareTo(other.Year) * BCMultiplier;
            if (this.Month != other.Month)
                return this.Month.CompareTo(other.Month) * BCMultiplier;
            if (this.Day != other.Day)
                return this.Day.CompareTo(other.Day) * BCMultiplier;

            return 0;
        }
    }

    public class NodeSorter : IComparer
    {
        public int Compare(object x, object y)
        {
            TreeNode tx = x as TreeNode;
            TreeNode ty = y as TreeNode;
            BCADDate a = BCADDate.Parse(tx.Text);
            BCADDate b = BCADDate.Parse(ty.Text);
            return a.CompareTo(b);
        }
    }
}

Codes used in my application is showing as below:

TreeView2.TreeViewNodeSorter = new NodeSorter();
McMillan Cheng
  • 382
  • 1
  • 6
  • 20