25 Dec 2011 @ 2:05 PM 

As I posted previously here , Sorting a Listview can be something of a pain in the butt.

In that article, I covered some basics on providing a class that would essentially give you sorting capabilities for free, without all the messy code that would normally be required. A lot of the code required for sorting is mostly boilerplate with a few modifications for sorting various types. As a result, the generic implementation works rather well.

However, as with any class, adding features never hurts. In this case, I got to thinking- why not have right-clicking the ColumnHeaders show a menu for sorting against that Column? Seems simple enough. I quickly learned that apparent simplicity often is misattributed.

I faced several issues. The first thought was that I could hook a Mouse event for Right-Clicking a column header. Unfortunately, I soon discovered two facts about the .NET ListView control. First, was that there was no event for right-clicking a header control. Second, no even was fired at all by the ListView control when you right-clicked a header.

This left me stymied. How the heck do I implement this feature? I discovered something of a “hack” however, in that when the ListView’s ContextMenuStrip property is set, that ContextMenu Strip will be shown regardless of the location the ListView is clicked. This at least gave me something to work with. Since a ContextMenuStrip’s “Opening” event can be easily hooked, we can use that as an entry point and perform needed calculations to determine if we are indeed on a columnheader.

Which brings me to the next problem, which is determining when a columnheader was in fact the item that was clicked. This requires determining the rectangle the Header control occupies, first. The Header Control is a child control of the ListView; as such, a platform Invoke using the EnumChildWindows() API was required, something like this:

 
private Rectangle _HeaderRect;
private delegate bool EnumWindowsCallBack(IntPtr hwnd,IntPtr lparam);
 [DllImport("user32.dll")] 
private static extern int EnumChildWindows(IntPtr hwndParent,EnumWindowCallBack callbackFunction,IntPtr lParam);
 [DllImport("user32.dll"] 
private static extern bool GetWindowRect(IntPtr hWnd,out RECT lpRect);

 [StructLayout(LayoutKind.Sequential)] 
private struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;

}

private bool EnumWindowCallback(IntPtr hwnd, IntPtr lParam)
{
    RECT rct;
    if(!GetWindowRect(hwnd,out rct))
    {
    //first child of the listview should be the header control
    _HeaderRect=Rectangle.Empty; //likely the listview is not in Details mode, so there is no header control.
    }
    else
    {
        _HeaderRect = new Rectangle(rct.Left,rct.Top,rct.Right-rct.Left,rct.Bottom-rct.Top);
    }
    return false; //cancel enumeration.
}
private static ColumnHeader []  GetOrderedHeaders(ListView lvw)
{
    ColumnHeader []  returnarray = new ColumnHeader [lvw.Columns.Count] ;
    foreach(ColumnHeader loopheader in lvw.Columns)
    { 
        returnarray [loopheader.DisplayIndex]  = loopheader;

    }
    return returnarray;
}
 

Quite a bit of boilerplate to add in. Basically, the idea is that we will hook the contextMenu Opening event of the Listview, (and we add a context menu to hook if the listview in fact doesn’t have one) in our constructor; and then when we receive the event we need to determine if the click occured within the area of the header control of the listview, if so, we cancel the event (which stops the default context menu from appearing) and show our own menu for the columnheader, which we can acquire using a bit of math and the static “GetOrderedHeaders” function, which retrieves the array of columnheaders of a ListView in order of appearance Left to Right (since the user could rearrange the Columns).

So First, we need to add code to the GenericListViewSorter’s Constructor. We also have a few private variables that are added; in this case, we need a ContextMenuStrip variable called “_ghostStrip” which we will use if we need to create a context menu for the control, since we don’t want that to appear in the default case. Of course we create our own ContextMenuStrip which we will show in the event instead of the default when appropriate. so we add this beneath the existing code in the constructor:

 
    if(handleListView.ContextMenuStrip==null)
    {
        handleListView.ContextMenuStrip = new ContextMenuStrip();
        handleListView.ContextMenuStrip.Items.Add("GHOST"); //add a ghost item so we get the Opening Event
        _ghoststrip = handleListView.ContextMenuStrip;
}

//create OUR context menu
_headerContextMenuStrip = new ContextMenuStrip();
//add a ghost item to make sure Opening will fire.
_HeaderContextMenuStrip.Items.Add("ghost");
handleListView.ContextMenuStrip.Opening += ContextMenuStrip_Opening;
handleListView.ContextMenuStripChanged += handleListView_ContextMenuStripChanged;


 

Of course we need to add the two referenced event handlers, too. The ContextMenuStripChanged being a rather simple implementation designed to keep changes in the contextmenu of the listview from causing us to balls up and stop showing ours (since we are now hooking a orphaned context menu not being shown by the listview).

 
void handleListView_ContextMenuStripChanged(object sender,EventArgs e)
{
    OurListView.ContextMenuStrip.Opening+=ContextMenuStrip_Opening;
}
 

Now the meat of the code is in the ContextMenuStrip_Opening() routine. This will need to determine wether its applicable to show the Column menu, or the already present menu (which it doesn’t show either if it happens to be the _ghoststrip). This is accomplished by use of the GetCursorPos() API routine paired with the already present GetWindowRect() implementation, which we update by calling EnumWindows.

 
void ContextMenuStrip_Opening(object sender,System.ComponentModel.CancelEventArgs e)
{
    //first, get screen coordinates of Cursor.
    POINTAPI gapi;
    GetCursorPos(out gapi);
    
    Point gotposition = new Point(gapi.X,gapi.Y);
    
    //acquire the HeaderRect of the control...
    EnumChildWindows(OurListView.Handle,new EnumWindowCallBack(EnumWindowCallback),IntPtr.Zero);
    //if the mouse position is within the retrieved rectangle, cancel the display of the normal menu and create and show ours.
    if(_HeaderRect.Contains(gotposition))
    {
        e.Cancel=true;
        int xoffset = gotposition.X - _HeaderRect.Left;
        ColumnHeader clickedheader = HeaderAtOffset(OurListView,xoffset);
        
        if(clickedheader != null)
        {
        //create the context menu as needed.
        _HeaderContextMenuStrip = new ContextMenuStrip();
        _HeaderContextMenuStrip.Tag = clickedheader;
        //two items, one for ascending order, one for descending order.
        ToolStripMenuItem AscendingHeaderItem = new ToolStripMenuItem(String.Format("Sort Column \"{0}\" Ascending",clickedheader.Text));
        ToolStripMenuItem DescendingHeaderItem = new ToolStripMenuItem(String.Format("Sort Column \"{1}\" Descending",clickedheader.Text));
        
        //if the current sort column is the header, check it off and disable it.
        
        if(CurrentSortColumn == clickedheader)
        {  
            if(OurListView.Sorting ==SortOrder.Ascending)
            {
                AscendingHeaderItem.Checked=true;
                AscendingHeaderItem.Enabled=false;
            }
            else if (OurListView.Sorting==SortOrder.Descending)
            {
              DescendingHeaderItem.Checked=true;
              DescendingHeaderItem.Enabled=false;
            }   
        
        }
        AscendingHeaderItem.Tag = ClickedHeader;
        DescendingHeaderItem.Tag = ClickedHeader;
        //set event handlers for the two items.
        AscendingHeaderItem.Click+= AscendingHeaderItem_Click;
        DescendingHeaderItem.Click+= DescendingHeaderItem_Click;
        //add them to the context menu strip.
        _HeaderContextMenuStrip.Items.Add(AscendingHeaderItem);
        _HeaderContextMenuStrip.Items.Add(DescendingHeaderItem);
        //display the menu.
        _HeaderContextMenuStrip.Show(gotposition);
        }
   } 
   else
   {
       //show the default menu, but only if it isn't the ghoststrip.
       if(OurListView.ContextMenuStrip == _ghoststrip)
           e.Cancel=true;
   
   }   

}


 

The events for the two buttons basically sort based on the columnheader in their tag, nothing particularly special there. the actual details can be seen in the source file itself, really.

It actually works quite well, I’m using it in a production application, and it’s working quite well.

Some obvious enhancements, of course, include making it possible to customize the shown menu, to present other options; perhaps a delegate or event that can be hooked that is given the Strip and the clicked column, and any number of other parameters? This would essentially give the equivalent of a ColumnHeaderRightClicked type event, too.

388 total views, no views today

Posted By: BC_Programming
Last Edit: 05 May 2012 @ 10:21 PM

EmailPermalinkComments (0)
Tags
 22 Dec 2011 @ 10:51 AM 

Or, at least that’s what I am calling it. The .NET framework provides quite a rich set of data structures for dealing with Dates. You’ve got DateTime which represents a single point in time, You’ve got TimeSpan which represents a interval.

However I noticed something somewhat absent- a DateTime Range, with a start time, and an ending time. This came about because I required a way to coalesce a set of DateTime’s organized as Starting and Ending time so that there weren’t any overlaps. For example, if I had datetimes for one interval from 9AM to 12PM, another from 8AM to 11AM, and a third from 5PM to 9PM, the coalesced result would be two intervals, one from 8AM to 12PM, and one from 5PM to 9PM. Basically, the point was to “simplify” a set of datetime intervals so that overlapping time would not be counted.

At first, I figured it would be built-in functionality of one of the DateTime classes. It’s not. so I did some googling, no real luck there either. So, I decided to write my own “DateRange” class that encapsulated a Start Time and an Ending time and had the functionality I required.

Obviously, the first thing we know is that the class is going to need Starting Time and Ending Time. Of course, once we’ve defined the data structures, the tricky part will be the “coalescing” code. The implementations for which can be found in the “CoalesceRanges” and “Coalesce” methods.

  1.  
  2.     public class DateRange : ICloneable
  3.  
  4.     {
  5.  
  6.  
  7.  
  8.         private DateTime _StartTime, _EndTime;
  9.  
  10.  
  11.  
  12.         public DateTime StartTime { get { return _StartTime;}}
  13.  
  14.         public DateTime EndTime { get { return _EndTime; }}
  15.  
  16.  
  17.  
  18.         public TimeSpan Span
  19.  
  20.         {
  21.  
  22.             get {
  23.  
  24.            
  25.  
  26.             return EndTime-StartTime;
  27.  
  28.             }
  29.  
  30.             set {
  31.  
  32.             _EndTime = StartTime+value;
  33.  
  34.            
  35.  
  36.             }
  37.  
  38.  
  39.  
  40.         }
  41.  
  42.         public DateRange(DateTime sTime, DateTime eTime)
  43.  
  44.         {
  45.  
  46.             if (sTime < eTime) _StartTime = sTime; else _StartTime=eTime;
  47.  
  48.             if (sTime > eTime) _EndTime = sTime; else _EndTime=eTime;
  49.  
  50.  
  51.  
  52.  
  53.  
  54.         }
  55.  
  56.         public override string ToString()
  57.  
  58.         {
  59.  
  60.             return "Range:(" + StartTime.ToString() + ")-(" + EndTime.ToString() + ")";
  61.  
  62.         }
  63.  
  64.         public static DateRange []  Coalesce(DateRange RangeA, DateRange RangeB)
  65.  
  66.         {
  67.  
  68.             bool CondA = RangeA.StartTime > RangeB.EndTime;
  69.  
  70.             bool CondB = RangeA.EndTime < RangeB.StartTime;
  71.  
  72.             bool overlaps = !(CondA || CondB);
  73.  
  74.  
  75.  
  76.  
  77.  
  78.            // bool overlaps = (RangeA.StartTime > RangeB.EndTime) && (RangeA.EndTime < RangeB.StartTime);
  79.  
  80.  
  81.  
  82.             //overlap exists if neither A or B is true.
  83.  
  84.             if (overlaps)
  85.  
  86.             {
  87.  
  88.                 //overlap exists.
  89.  
  90.                 //create a new starttime the lower of the given two…
  91.  
  92.                 DateTime newstart, newEnd;
  93.  
  94.  
  95.  
  96.                 if (RangeA.StartTime < RangeB.StartTime) newstart = RangeA.StartTime; else newstart = RangeB.StartTime;
  97.  
  98.                 if (RangeA.EndTime > RangeB.EndTime) newEnd = RangeA.EndTime; else newEnd = RangeB.EndTime;
  99.  
  100.  
  101.  
  102.                 return new DateRange []  { new DateRange(newstart, newEnd) };
  103.  
  104.  
  105.  
  106.             }
  107.  
  108.             else
  109.  
  110.             {
  111.  
  112.                 //there is no overlap, return a daterange array with both the same elements.
  113.  
  114.                 return new DateRange []  { (DateRange)RangeA.Clone(), (DateRange)RangeB.Clone() };
  115.  
  116.  
  117.  
  118.             }
  119.  
  120.  
  121.  
  122.  
  123.  
  124.         }
  125.  
  126.  
  127.  
  128.         public DateRange []  Coalesce(DateRange Otherdaterange)
  129.  
  130.         {
  131.  
  132.            
  133.  
  134.             return DateRange.Coalesce(this, Otherdaterange);
  135.  
  136.  
  137.  
  138.         }
  139.  
  140.  
  141.  
  142.  
  143.  
  144.  
  145.  
  146.  
  147.  
  148.         public static DateRange []  CoalesceRanges(DateRange []  ranges)
  149.  
  150.         {
  151.  
  152.             //coalesces/simplifies a given set of date ranges to the simplest form. For example:
  153.  
  154.            /*   a
  155.  
  156.             * |—-|
  157.  
  158.             *     b
  159.  
  160.             *   |—-|       c
  161.  
  162.             *              |—-|
  163.  
  164.             * */
  165.  
  166.             //will return an array of two Dateranges:
  167.  
  168.  
  169.  
  170.             /*    a
  171.  
  172.              * |—–|
  173.  
  174.              *               b
  175.  
  176.              *             |—-|
  177.  
  178.              
  179.  
  180.              * */
  181.  
  182.             //this is the "simplest" reduction of what the previous ranges were.
  183.  
  184.              List<daterange> userange = ranges.ToList();
  185.  
  186.  
  187.             List<daterange> workalist = userange;
  188.  
  189.             List<daterange> result = new List<daterange>();
  190.  
  191.  
  192.  
  193.            
  194.  
  195.             //algorithm
  196.  
  197.             //set a flag indicating a coalesce was discovered to true.
  198.  
  199.             //iterate while said flag is true.
  200.  
  201.  
  202.  
  203.             //set flag to false as loop begins.
  204.  
  205.             //iterate through every possible pair X and Y in the List. skip iterations where X==Y.
  206.  
  207.  
  208.  
  209.             //for each possible pairing:
  210.  
  211.             //coalesce the two DateRanges. If the results coalesce (the return value array has a length of 1)
  212.  
  213.             //mark x and y for removal, set the new Array to be AddRanged() to the List, and break out of both loops, and set the flag saying that
  214.  
  215.             //a coalesce was discovered.
  216.  
  217.            
  218.  
  219.             bool coalescefound =true;
  220.  
  221.             while (coalescefound)
  222.  
  223.             {
  224.  
  225.                 List<daterange> removalmarked = new List<daterange>();
  226.  
  227.                 List<daterange> addmarked = new List<daterange>();
  228.  
  229.                 bool breakouter=false;
  230.  
  231.                 coalescefound=false;
  232.  
  233.                 foreach (DateRange x in workalist)
  234.  
  235.                 {
  236.  
  237.                     foreach (DateRange y in workalist)
  238.  
  239.                     {
  240.  
  241.                         if (x != y)
  242.  
  243.                         {
  244.  
  245.                             DateRange []  coalresult = DateRange.Coalesce(x, y);
  246.  
  247.  
  248.  
  249.                             if (coalresult.Length == 1)
  250.  
  251.                             {
  252.  
  253.                                 coalescefound=true;
  254.  
  255.                                 //add new array to be added…
  256.  
  257.                                 addmarked.AddRange(coalresult);
  258.  
  259.                                 //remove both x and y. We can’t remove them here since we are iterating…
  260.  
  261.                                 removalmarked.Add(x);
  262.  
  263.                                 removalmarked.Add(y);
  264.  
  265.                                 breakouter=true;
  266.  
  267.                                 break;
  268.  
  269.  
  270.  
  271.                             }
  272.  
  273.  
  274.  
  275.  
  276.  
  277.  
  278.  
  279.                         }
  280.  
  281.                        
  282.  
  283.  
  284.  
  285.                     }
  286.  
  287.  
  288.  
  289.                     if (breakouter) break; //break out of outer iterator if flag set.
  290.  
  291.  
  292.  
  293.                 }
  294.  
  295.                 //add and remove the marked items.
  296.  
  297.                 foreach (var removeme in removalmarked)
  298.  
  299.                 {
  300.  
  301.  
  302.  
  303.                     workalist.Remove(removeme);
  304.  
  305.  
  306.  
  307.                 }
  308.  
  309.                 foreach (var addme in addmarked)
  310.  
  311.                 {
  312.  
  313.                     workalist.Add(addme);
  314.  
  315.  
  316.  
  317.                 }
  318.  
  319.                
  320.  
  321.  
  322.  
  323.  
  324.  
  325.  
  326.  
  327.  
  328.  
  329.             }
  330.  
  331.  
  332.  
  333.  
  334.  
  335.  
  336.  
  337.             return workalist.ToArray();
  338.  
  339.  
  340.  
  341.  
  342.  
  343.  
  344.  
  345.  
  346.  
  347.  
  348.  
  349.  
  350.  
  351.  
  352.  
  353.  
  354.  
  355.  
  356.  
  357.  
  358.  
  359.  
  360.  
  361.  
  362.  
  363.  
  364.  
  365.  
  366.  
  367.  
  368.  
  369.  
  370.  
  371.         }
  372.  
  373.  
  374.  
  375.         public object Clone()
  376.  
  377.         {
  378.  
  379.             return new DateRange(StartTime, EndTime);
  380.  
  381.         }
  382.  
  383.     }
  384.  

For this case I’ve decided that I would make the class Immutable. This should make our accessors simpler to implement. As a result, the StartTime and EndTime implementations only define get accessors. I also add a “Span” parameter that actually does change the EndTime in it’s setter, so I guess I lied. Oh well. Anyways, the meat of the class is in the various coalescing methods, which makes sense since that is what I sort of wrote the class to do. the class could surely be fleshed out with other, useful methods and properties, but as it is now it meets my own needs admirably so I’ve not really expanded it past what I’ve got here.

876 total views, no views today

Posted By: BC_Programming
Last Edit: 22 Dec 2011 @ 10:58 AM

EmailPermalinkComments (0)
Tags
Categories: Programming
 21 Dec 2011 @ 8:24 PM 

Anybody who has used windows is probably familiar with the ListView control. It is used in Windows Explorer; it is even used for the desktop. Heck, the ListView control even has implementations on Linux and Mac, and in the latter case it was there first.

The ListView itself can display in several modes. Normally, it shows things as Icons. But it can also be set to show Small Icons, a List, in some Operating Systems, there is a ‘Tile’ option, or even options like Large,Medium, and other sizes of Icons. My Personal favourite is the details mode.

HA! Bet you didn't expect me to use an image from Windows 95! Expect the unexpected, chaps.

Because I mostly see and use Listviews in Details mode, I also force people who use my software to deal with Details mode. Mostly because the reason I am displaying a ListView is to show some data in a somewhat tabular format and not just give them a few icons to drag around with minimal actual information, but I digress. Anyway, I think a good question at this point might be to look at what different parts this particular ListView has. First, the gray “buttons” at the top, which serve to title each column, are referred to affectionately as ColumnHeaders. Under each ColumnHeader there is data for a given “subitem” of each item. For example, the “Size” entry here is a Subitem for each drive. An interesting feature of columnheaders that is nearly universal is that you can click on one, and it will sort by that column.

Another interesting thing, is that in many programming environments, the ListView control doesn’t actually provide this feature for you, and you have to code it yourself. It is rather frustrating. In particular, Visual Basic 6 allows you to sort, but you can’t really customize what you sort by; it always treats it as text. In one of my VB6 applications, BCSearch (which is available for download from my Downloads page) I managed to use a Custom control, available from VBAccelerator.com, which exposes additional functionality of the ListView Control on top of that provided in either of the MS provided libraries for use within Visual Basic. One of these features is that it has better support for sorting. I still had to add my own “arrow” to show the sort direction, though.

BCSearch showing results sorted by Size

The VBAccelerator control exposes a number of events and properties for controlling sorting, which I use to properly sort the various subitems, so that various entries like date or size aren’t sorted as text.

Curiously, the .NET Windows Forms ListView control, while having more functionality, still leaves a lot of effort to the programmer for what ideally ought to be a free feature supported by the OS. In fact it IS a free feature supported by the OS. Thankfully, the .NET control does in fact provide a feature for customizing sort functionality, And all you need is a class to implement IComparer. the IComparer will be used to compare the listitems as the Listview sorts. But if you have, say, Date and Time fields and size fields or other fields that can’t just be sorted as text, you are going to need to implement your own special comparer for each. This amounts to quite a bit of glue code; on top of that, you will need to handle the ColumnClick events on the ColumnHeader, change the sort mode, and sort it, and so forth.

To combat this bloating code, I wrote a relatively small class designed to encapsulate sorting. The idea being that you create a instance of this class for each listview, pass in the ListView to it’s constructor, and the class handles all the details. It worked quite well. There was a minor issue that amounted to a gigantic pain in the ass but at the same time made the result a lot better.

  1.  
  2. using System;
  3.  
  4. using System.Collections;
  5.  
  6. using System.Collections.Generic;
  7.  
  8. using System.Diagnostics;
  9.  
  10. using System.Drawing;
  11.  
  12. using System.Linq;
  13.  
  14. using System.Runtime.InteropServices;
  15.  
  16. using System.Text;
  17.  
  18. using System.Windows.Forms;
  19.  
  20.  
  21.  
  22. namespace JobClockAdmin
  23.  
  24. {
  25.  
  26.    
  27.  
  28.     public static class ListViewExtensions
  29.  
  30.     {
  31.  
  32.          [System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential)]
  33.  
  34.         public struct HDITEM
  35.  
  36.         {
  37.  
  38.             public int mask;
  39.  
  40.             public int cxy;
  41.  
  42.              [System.Runtime.InteropServices.MarshalAs(UnmanagedType.LPTStr)]
  43.  
  44.             public string pszText;
  45.  
  46.             public IntPtr hbm;
  47.  
  48.             public int cchTextMax;
  49.  
  50.             public int fmt;
  51.  
  52.             public IntPtr lParam;
  53.  
  54.             // _WIN32_IE >= 0×0300
  55.  
  56.             public int iImage;
  57.  
  58.             public int iOrder;
  59.  
  60.             // _WIN32_IE >= 0×0500
  61.  
  62.             public uint type;
  63.  
  64.             public IntPtr pvFilter;
  65.  
  66.             // _WIN32_WINNT >= 0×0600
  67.  
  68.             public uint state;
  69.  
  70.  
  71.  
  72.              [Flags]
  73.  
  74.             public enum Mask
  75.  
  76.             {
  77.  
  78.                 Format = 0×4,  // HDI_FORMAT
  79.  
  80.             };
  81.  
  82.  
  83.  
  84.              [Flags]
  85.  
  86.             public enum Format
  87.  
  88.             {
  89.  
  90.                 SortDown = 0×200,   // HDF_SORTDOWN
  91.  
  92.                 SortUp = 0×400,     // HDF_SORTUP
  93.  
  94.             };
  95.  
  96.         };
  97.  
  98.         private const int HDM_FIRST = 0×1200;
  99.  
  100.         private const int LVM_FIRST = 0×1000;
  101.  
  102.         private const int HDM_GETITEMCOUNT = (HDM_FIRST + 0);
  103.  
  104.         private const int HDM_SETITEM = (HDM_FIRST + 4);
  105.  
  106.  
  107.  
  108.         private const int LVM_GETHEADER = (LVM_FIRST + 31);
  109.  
  110.         private const int HDM_GETITEM = (HDM_FIRST + 3);
  111.  
  112.          [DllImport("user32.dll", EntryPoint = "SendMessageA")]
  113.  
  114.         private static extern IntPtr SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);
  115.  
  116.  
  117.  
  118.  
  119.  
  120.          [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SendMessage")]
  121.  
  122.         public static extern IntPtr SendMessageHDITEM(IntPtr hWnd, uint Msg, IntPtr wParam, ref HDITEM hdItem);
  123.  
  124.  
  125.  
  126.  
  127.  
  128.         public static void SetSortIcon(this System.Windows.Forms.ListView ListViewControl, int ColumnIndex, System.Windows.Forms.SortOrder Order)
  129.  
  130.         {
  131.  
  132.             IntPtr ColumnHeader = SendMessage(ListViewControl.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero);
  133.  
  134.  
  135.  
  136.             for (int ColumnNumber = 0; ColumnNumber < = ListViewControl.Columns.Count1; ColumnNumber++)
  137.  
  138.             {
  139.  
  140.                 IntPtr ColumnPtr = new IntPtr(ColumnNumber);
  141.  
  142.                 HDITEM item = new HDITEM();
  143.  
  144.                 item.mask = (int)HDITEM.Mask.Format;
  145.  
  146.                 SendMessageHDITEM(ColumnHeader, HDM_GETITEM, ColumnPtr, ref item);
  147.  
  148.  
  149.  
  150.                 if (!(Order == System.Windows.Forms.SortOrder.None) && ColumnNumber == ColumnIndex)
  151.  
  152.                 {
  153.  
  154.                     switch (Order)
  155.  
  156.                     {
  157.  
  158.                         case System.Windows.Forms.SortOrder.Ascending:
  159.  
  160.                             item.fmt &= ~(int)HDITEM.Format.SortDown;
  161.  
  162.                             item.fmt |= (int)HDITEM.Format.SortUp;
  163.  
  164.                             break;
  165.  
  166.                         case System.Windows.Forms.SortOrder.Descending:
  167.  
  168.                             item.fmt &= ~(int)HDITEM.Format.SortUp;
  169.  
  170.                             item.fmt |= (int)HDITEM.Format.SortDown;
  171.  
  172.                             break;
  173.  
  174.                     }
  175.  
  176.                 }
  177.  
  178.                 else
  179.  
  180.                 {
  181.  
  182.                     item.fmt &= ~(int)HDITEM.Format.SortDown & ~(int)HDITEM.Format.SortUp;
  183.  
  184.                 }
  185.  
  186.  
  187.  
  188.                 SendMessageHDITEM(ColumnHeader, HDM_SETITEM, ColumnPtr, ref item);
  189.  
  190.             }
  191.  
  192.         }
  193.  
  194.     }
  195.  
  196.  
  197.  
  198.  
  199.  
  200.  
  201.  
  202.  
  203.  
  204.     class GenericListViewSorter : IComparer
  205.  
  206.     {
  207.  
  208.         private System.Windows.Forms.ListView OurListView;
  209.  
  210.  
  211.  
  212.  
  213.  
  214.         //GetCompareValue: given a columnname and a ListViewItem, should return any more specific type.
  215.  
  216.         //For example, if Column represents a date value, it would return a DateTime.
  217.  
  218.         public delegate Object GetCompareValue(GenericListViewSorter Sorter, String ColumnName, ListViewItem Item);
  219.  
  220.  
  221.  
  222.         private GetCompareValue CompareValueFunc;
  223.  
  224.         private ColumnHeader CurrentSortColumn = null;
  225.  
  226.         private SortOrder []  SortOrders = new SortOrder []  { SortOrder.None,SortOrder.Ascending, SortOrder.Descending };
  227.  
  228.         private String []  SortOrderImageKey = new string []  {"CLEAR","ASCENDING","DESCENDING" };
  229.  
  230.         private int CurrSortIndex = 0;
  231.  
  232.  
  233.  
  234.        
  235.  
  236.  
  237.  
  238.  
  239.  
  240.         private Object GetCompareValue_Default(GenericListViewSorter Sorter, String ColumnName, ListViewItem Item)
  241.  
  242.         {
  243.  
  244.             //default just returns the String, for now. Later, add special conditions that detect when something is a valid date. Or something…
  245.  
  246.             int indexuse = Sorter.OurListView.Columns [ColumnName] .Index;
  247.  
  248.             return Item.SubItems [indexuse] .Text;
  249.  
  250.            
  251.  
  252.  
  253.  
  254.         }
  255.  
  256.  
  257.  
  258.         public GenericListViewSorter(ListView handleListView,GetCompareValue GetCompareRoutine)
  259.  
  260.         {
  261.  
  262.             OurListView = handleListView;
  263.  
  264.            
  265.  
  266.             if (GetCompareRoutine != null)
  267.  
  268.                 CompareValueFunc = GetCompareRoutine;
  269.  
  270.             else
  271.  
  272.                 CompareValueFunc = GetCompareValue_Default;
  273.  
  274.  
  275.  
  276.             handleListView.ColumnClick += new ColumnClickEventHandler(handleListView_ColumnClick);
  277.  
  278.            
  279.  
  280.                
  281.  
  282.              
  283.  
  284.         }
  285.  
  286.  
  287.  
  288.         void handleListView_ColumnClick(object sender, ColumnClickEventArgs e)
  289.  
  290.         {
  291.  
  292.             //throw new NotImplementedException();
  293.  
  294.             //First thing is first: is this the same column that was clicked before?
  295.  
  296.             ColumnHeader clickedcolumn = OurListView.Columns [e.Column] ;
  297.  
  298.             if (CurrentSortColumn == null)
  299.  
  300.             {
  301.  
  302.                 CurrentSortColumn = clickedcolumn;
  303.  
  304.  
  305.  
  306.             }
  307.  
  308.             if (CurrentSortColumn != clickedcolumn)
  309.  
  310.             {
  311.  
  312.                 //if not, set the current sort Index to 0…
  313.  
  314.                
  315.  
  316.                // CurrentSortColumn.ImageKey = "CLEAR"; //don’t want it to keep the image…
  317.  
  318.                
  319.  
  320.                 CurrentSortColumn = clickedcolumn;
  321.  
  322.  
  323.  
  324.             }
  325.  
  326.             else
  327.  
  328.             {
  329.  
  330.                 //if it is the same, increment it and take the modulus…
  331.  
  332.  
  333.  
  334.                 CurrSortIndex = (CurrSortIndex + 1) % (SortOrders.Length);
  335.  
  336.                 Debug.Print("CurrSortIndex:" + CurrSortIndex);
  337.  
  338.  
  339.  
  340.             }
  341.  
  342.            
  343.  
  344.             //CurrentSortColumn.ImageKey = SortOrderImageKey [CurrSortIndex] ;
  345.  
  346.            
  347.  
  348.             OurListView.Sorting = SortOrders [CurrSortIndex] ;
  349.  
  350.             OurListView.SetSortIcon(CurrentSortColumn.Index, OurListView.Sorting);
  351.  
  352.             if (CurrSortIndex == 0)
  353.  
  354.             {
  355.  
  356.                 OurListView.ListViewItemSorter = null;
  357.  
  358.             }
  359.  
  360.             else
  361.  
  362.             {
  363.  
  364.                 OurListView.ListViewItemSorter = this;
  365.  
  366.             }
  367.  
  368.             OurListView.Sort();
  369.  
  370.         }
  371.  
  372.  
  373.  
  374.         #region IComparer Members
  375.  
  376.  
  377.  
  378.         public int Compare(object x, object y)
  379.  
  380.         {
  381.  
  382.             ListViewItem a = (ListViewItem)x;
  383.  
  384.             ListViewItem b = (ListViewItem)y;
  385.  
  386.             String columnnameuse = CurrentSortColumn.Name;
  387.  
  388.  
  389.  
  390.  
  391.  
  392.             Object checkA = CompareValueFunc(this, columnnameuse, a);
  393.  
  394.             Object checkB = CompareValueFunc(this, columnnameuse, b);
  395.  
  396.  
  397.  
  398.  
  399.  
  400.             if((checkA is IComparable) && (checkB is IComparable))
  401.  
  402.             {
  403.  
  404.                 if(OurListView.Sorting==SortOrder.Ascending)
  405.  
  406.                     return ((IComparable)checkA).CompareTo(checkB);
  407.  
  408.                 else
  409.  
  410.                 {
  411.  
  412.                     return ((IComparable)checkB).CompareTo(checkA);
  413.  
  414.                 }
  415.  
  416.  
  417.  
  418.             }
  419.  
  420.  
  421.  
  422.             return 0;
  423.  
  424.  
  425.  
  426.  
  427.  
  428.  
  429.  
  430.  
  431.  
  432.         }
  433.  
  434.  
  435.  
  436.         #endregion
  437.  
  438.     }
  439.  
  440. }
  441.  

As you can see, it it relatively small (overall). the API code at the top might be a bit confusing, but it is a result of what can only be described as an oversight on Microsoft’s part; see, originally, I was changing the sort arrow header by simply changing the columnheader image. This worked, sorta of, but there was no way to remove the image and it had this weird effect where it would basically move the text and make it aligned sorta weird. Turns out that the way the ListView would “normally” show sort order icons was a built in feature of the Listview since Common Controls 6 (XP). After some SDK digging I was able to use the Platform Invoke feature of C# to call all the appropriate API functions and “force” the Listview to show the sort order in the header appropriately.

The class also exposes a custom delegate which can be implemented and passed in to the constructor, which will allow for “custom” sorts. This is useful if columns contain data like dates, or numbers that you don’t want to be sorted using the normal “text” comparison.

All in all, It’s a class I’ve added to my “toolbox”, alongside my INIFile class for accessing INI Files. did I write about that one? I forget.

346 total views, no views today

Posted By: BC_Programming
Last Edit: 21 Dec 2011 @ 08:24 PM

EmailPermalinkComments (0)
Tags
 16 Dec 2011 @ 12:10 PM 

Most Computer users are familiar with the Sounds that Windows emits when you plug and unplug a USB thumb drive. It’s a useful form of auditory feedback that the drive was in fact detected. However, I’ve found linux to be oddly tacit in this regard. So I set to work writing a python script that uses DBUS to monitor for new USB devices and will play a sound whenever a new Volume is attached.

  1.  
  2. #!/usr/bin/python
  3. #by BC_Programming
  4. import dbus
  5. import gobject
  6. import time
  7. import subprocess
  8.  
  9. print "BASeCamp ‘USBSounds’ Simple USB Volume notification sound."
  10.  
  11. #playsound merely shells out to mplayer. I would have preferred an integrated solution but… meh.
  12. def playsound(soundfile):
  13.  
  14.     subprocess.call( ["mplayer", "hardwareinsert.wav"] , stdout=open(‘/dev/null’, ‘w’), stderr=open(‘/dev/null’, ‘w’))
  15.    
  16. class DeviceAddedListener:
  17.     def __init__(self):
  18.         #get the system bus…
  19.         self.bus = dbus.SystemBus()
  20.         #get the manager
  21.         self.hal_manager_obj = self.bus.get_object(
  22.                                                   ‘org.freedesktop.Hal’,
  23.                                                   ‘/org/freedesktop/Hal/Manager’)
  24.         #get the interface for the manager                                          
  25.         self.hal_manager = dbus.Interface(self.hal_manager_obj,‘org.freedesktop.Hal.Manager’)
  26.         #connect to the appropriate signals.
  27.         self.hal_manager.connect_to_signal(‘DeviceAdded’, self._filteradd)
  28.         self.hal_manager.connect_to_signal("DeviceRemoved",self._filterremove)
  29. #note: I couldn’t get DeviceRemoval sounds to work since it doesn’t let you     #inspect whether the removed device is a volume via "QueryCapability"… since it’s gone.
  30.     def _filteradd(self, udi):
  31.         device_obj = self.bus.get_object (‘org.freedesktop.Hal’, udi)
  32.         device = dbus.Interface(device_obj, ‘org.freedesktop.Hal.Device’)
  33.         #if it is a volume, call the do_add function…
  34.         if device.QueryCapability("volume"):
  35.             return self.do_add(device)
  36.            
  37.     def _filterremove(self,udi):
  38.         try:
  39.             #device_obj = self.bus.get_object(‘org.freedesktop.Hal’,udi)
  40.             #device = dbus.Interface(device_obj,’org.freedesktop.Hal.Device’)
  41.  
  42.             #if device.QueryCapability("volume"):
  43.             #    return self.do_remove(device)
  44.         except:
  45.             return
  46.     #unused….
  47.     def do_remove(self,volume):
  48.  
  49.         playsound("hardwareremove.wav")        
  50.         #displays some info about the added device to the console (maybe future changes can pop stuff like volume label, device file, size, etc into a Notification box?)
  51.     def do_add(self, volume):
  52.         device_file = volume.GetProperty("block.device")
  53.         label = volume.GetProperty("volume.label")
  54.         fstype = volume.GetProperty("volume.fstype")
  55.         mounted = volume.GetProperty("volume.is_mounted")
  56.         mount_point = volume.GetProperty("volume.mount_point")
  57.         try:
  58.             size = volume.GetProperty("volume.size")
  59.         except:
  60.             size = 0
  61.  
  62.         print "New storage device detected:"
  63.         print "  device_file: %s" % device_file
  64.         print "  label: %s" % label
  65.         print "  fstype: %s" % fstype
  66.         if mounted:
  67.             print "  mount_point: %s" % mount_point
  68.         else:
  69.             print "  not mounted"
  70.         print "  size: %s (%.2fGB)" % (size, float(size) / 1024**3)
  71.         #and play a sound.
  72.         playsound("hardwareinsert.wav")
  73.  
  74. #main loop…
  75. if __name__ == ‘__main__’:
  76.     from dbus.mainloop.glib import DBusGMainLoop
  77.     DBusGMainLoop(set_as_default=True)
  78.     loop = gobject.MainLoop()
  79.     print "in __main__…"
  80.     DeviceAddedListener()
  81.     loop.run()
  82.  

As can be seen, it’s a tad messy, and even rather hackish. For one thing, it uses DBUS, which to my understanding is deprecated. Unfortunately, the replacement I couldn’t really get a clear answer on. From what I can gather, the proper method for now is libnotify and pynotify, but I couldn’t get libnotify to compile and thus was not able to properly use pynotify, and I didn’t want to have to force people to go through that sort of hell when they tried to use my script, so I stuck to DBUS.

The only limitation I discovered is that on device removal, you can’t really inspect what device was removed. At first I just figured, Just play the sound everytime and let the user figure it out, but for some reason that just assaulted me with constant device removal sounds. So I ended up commenting (and I think removing) that particular segment of code.

Playing Sounds is unnecessarily difficult in Python, or more specifically, Linux. It’s ridiculous. First I found a build in module for python, ossdevsound (or something to that effect), but attempts to use that failed because apparently it uses OSS, which apparently was replaced by ALSA for whatever reason. So I tried pygame, which errored out that I had no mixer device when I tried to initialize the mixer. So I decided to hell with it and just spawned a mplayer process, and redirected it’s stdout to NULL to avoid the nasty business where it barfs all over the console. And amazingly, that seems to work fine for device insertions, which I decided I was content with.

By default I use the Windows insertion and removal sound files. The removal sound isn’t actually used but I kept it in the g-zipped tar because I wanted to. Personally I usually just launch this in a terminal and then tuck it away on another desktop. No doubt one can execute it as a daemon or something instead and get the functionality without the console window baggage to keep around, though.

ThumbNotify.tar.gz

596 total views, no views today

Posted By: BC_Programming
Last Edit: 16 Dec 2011 @ 12:10 PM

EmailPermalinkComments (0)
Tags
 16 Dec 2011 @ 11:51 AM 

One feature of windows that I often miss while using My Linux laptop is the ability to eject USB drives from the system tray. With my Linux Mint 10 install (and likely newer versions, and of course other distros) you typically need to minimize everything to get to the desktop (or of course use the shortcut, but you still need to minimize everything) get to the icon, right click it, choose to eject it, etc. With windows, you simply Left-Click on the icon in the Notification area and click the drive to remove.

Thankfully, It turns out there is a very cool and unassuming little program that provides this exact functionality. It is called “ejecter”.

Without dwelling on the fact that it really ought to be called Ejector, the program does exactly what it says on the tin. When you plug in a USB drive, the icon appears on the system tray.

Ejecter's ejection tray flyout. Or menu.

As can probably be guessed, this is exactly the type of functionality I needed. You can see exactly what the product looks like in the image to the left; when you left-click the icon, a little tray pops out (some might call it a menu, I suppose) and lists the connected USB drives that can be “Safely removed”. I rather like how it shows the Volume name as well as the name of the device itself, which helps prevent confusion. Ejecting the device entails clicking the Eject button to the right of the device you want to eject. Two clicks and you’re done!

 

It’s very simple to install, to- it’s in the repository of most distros; usually it’s a quick sudo apt-get install ejecter or the equivalent command (I remember yum but not the syntax). It works on GNOME, but I’m not sure if it works with KDE, or any other Desktop environment, for that matter.

268 total views, no views today

Posted By: BC_Programming
Last Edit: 16 Dec 2011 @ 11:51 AM

EmailPermalinkComments (0)
Tags
 16 Dec 2011 @ 11:19 AM 

It’s a relatively trivial task, really easy to do with the command prompt and GNU wc:

  1.  
  2. for /f "delims=" %P in (‘dir /s /b *.cs’) do wc -l "%P" >> D:\codewordcounts.txt
  3.  

I executed this within the desired directory (my BASeBlock source folder, if you must know) and the result was a file filled with numbers and files; I wrote a quick python script to parse that and add up the numbers that were at the start of each line, but then I figured, why not just write the whole think in python and forget about the rest of it, so I did.

  1.  
  2. #!/usr/bin/python
  3.  
  4. import sys
  5.  
  6. import fnmatch
  7.  
  8. import os
  9.  
  10.  
  11.  
  12. def searchfolder(folder,filemask,excludemask):
  13.  
  14.     filelist= []
  15.  
  16.     for root,subFolders,files in os.walk(folder):
  17.  
  18.         for file in files:
  19.  
  20.             buildpath=os.path.join(root,file)
  21.  
  22.             if os.path.isfile(buildpath):            
  23.  
  24.                 if fnmatch.fnmatch(buildpath,filemask):
  25.  
  26.                     if not fnmatch.fnmatch(buildpath,excludemask):
  27.                         filelist.append(buildpath)
  28.  
  29.     return filelist
  30.  
  31.    
  32.  
  33.  
  34.  
  35.  
  36.  
  37. def Countlines(filename):
  38.  
  39.     #counts the lines in filename.
  40.  
  41.     countit = open(filename)
  42.  
  43.     runner=0
  44.  
  45.     for line in countit:
  46.  
  47.         runner=runner+1
  48.  
  49.        
  50.  
  51.     return runner
  52.  
  53.    
  54.  
  55.    
  56.  
  57.  
  58.  
  59.  
  60.  
  61. searchfor = sys.argv [1]
  62.  
  63. searchin = sys.argv [2]
  64.  
  65. excludethese = sys.argv [3]
  66. print "examining files matching " + searchfor + " in directory " + searchin
  67.  + " excluding " + excludethese
  68. totalcount=0
  69.  
  70. for countthese in searchfolder(searchin,searchfor,excludethese):
  71.  
  72.     #print Countlines(countthese)
  73.  
  74.     totalcount = totalcount + Countlines(countthese)
  75.  
  76.  
  77.  
  78. print "total line count:" + totalcount
  79.  
  80.  
  81.  
  82.  
  83.  

It’s a rather basic script, and I don’t even comment it as much as I ought to. I just wanted a quick tool to be able to count the lines of code in a given directory for a given source file type. Ideally, I’d allow for multiple types, but I didn’t want to complicate the argument parsing code too much. The counting method is pretty barren, it just loops over every line and increments a counter. It seems to work relatively fast. It quickly gave me the result I wanted, which was that BASeBlock’s .cs files comprise about 53K lines of code, excluding the .designer.cs files (thus the third argument). And now I have a nice reusable script to figure this out in a jiffy without too much thinking about shell syntax or what I need to pipe to wc and what arguments I need to pass wc and whatnot. plonked in a location on my windows machine with pathext set to allow execution of .py files directly using the ActivePython interpreter and putting it on my Linux machine and adding a symlink in /usr/bin to it makes it available to me on both machines.

336 total views, no views today

Posted By: BC_Programming
Last Edit: 16 Dec 2011 @ 11:19 AM

EmailPermalinkComments (0)
Tags
 13 Dec 2011 @ 10:04 AM 

I don’t know how helpful this will be, but it sort of surprised me.

Basically, my brother has managed to go through three PS3 consoles. Each time, being the hardware expert he is – the type that would, when my 486 wasn’t booting up, open it up and make sure every connection was plugged into something – decided he could fix it himself. I think the issue was it wasn’t reading discs or something. Of course my advice was to send the bloody thing to Sony, but hey it was his warranty to void. What ended up happening of course was he ripped the entire thing apart, had absolutely no idea what he was doing and he ended up having to buy a new one since that one was no longer applicable for service. Anyway, I stumbled on the picked apart carcass of his old PS3- and I remembered that they have hard drives. So I opened up the HD access panel, took out the HD, and to my surprise I found it was just a 2.5″ SATA drive. To confirm this I plopped it into my laptop and installed Mint 12 on it. It’s mine now, heh. I’m not sure where his other picked part carcasses are, though. It’s a shame this laptop only allows for the installation of one Hard Drive, too.

Anyway, I didn’t know that they were so interchangable with PC parts in this manner, so maybe others might not be aware of it either. And I know quite a few people with dead consoles (PS3/XBox 360, etc) that they have basically shelved and forgotten about so if somebody needs an emergency Hard Drive this could be a useful nugget of info.

On a related Note, Mint 12 is extremely impressive… Although it primarily Reminded me just how heavily I customized the Mint 10 installation I was used to using on my laptop. The changes were mostly UI and I couldn’t figure out how to get my beloved Emerald working with a few quick googles so I swapped the drives back over. Now I could have messed about with Mint 12 by simply using the Live CD, but the Live CD is always somewhat slow and hardly really shows the OS at it’s true potential. And of course you can’t really add anything or make many changes to it, since it’s booting from a Read-Only medium.

Regarding Console Systems, though; is it just me, or are they basically just re-purposed PCs? The Xbox and Xbox360 are quite literally PC hardware specially built for handling gaming tasks, with specific software and also firmware “locks” to try to keep nosey people from finding out it’s really just a PC. This isn’t so bad, but it’s sort of stupid- I mean, really, the original XBox is essentially a Pentium 3 PC; The controller ports are just freakazoid USB connectors that they purposely changed just so they won’t be USB connections,and possibly to make them stay in better, USB ZIF slots aren’t what I would call the greatest for controllers. On the other hand, why change the entire pinout configuration- why couldn’t they have simply added some sort of additional mechanical connection that made them stay in better? And all the fancy crap about locking the Hard Drives from being changed by the user, and so forth is sort of silly. It doesn’t make a whole lot of sense to artificially limit what the device is capable of simply because you charge less for it than an equivalently configured PC.

And with all the add-ons for Console machines, such as keyboards, support for USB controllers, Hard Drives, Ethernet; the only real difference between consoles and PCs is that consoles always have the exact same hardware (things like GPU and CPU) that software developers can expect, whereas PCs have widely varying hardware; also, the Consoles are purposely locked down for reasons I can only guess.

This is all well and good, but as I noted, my Brother has gone through at least 3 Playstation 3 consoles. He wasn’t throwing them around the room or anything, I doubt he was abusive to them at all. And yet- they stopped working in one way or another. The failures of Xbox machines is no less of a problem. Meanwhile, my Super Nintendo is 20 years old and still works perfectly fine. A commonly cited “excuse” is that the machines are more complicated. Well, these people need to take a good hard look at the schematics for the various SNES ASIC chips and perhaps re-evaluate their definition of complicated. The only change is that newer consoles have more mechanical parts and they generate more heat and are squashed into as small a form-factor as possible. It has nothing to do with them being “more complicated” and everything to do with them being built out cheaper components than a PC (to justify the lower price point) and makes all hardware issues “non-user servicable”, unlike, say, a PC. This was a acceptable policy for things like the SNES or the Sega Genesis or earlier consoles of that nature; most of the issues that those consoles have are the result of loose connections that typically require Soldering knowledge to fix properly. But now, that sort of policy is sort of silly, since a lot of the problems with modern consoles are relatively simply in comparison, and many enthusiasts who know what the issue is could fix it themselves, if the machines themselves weren’t put together in a way that dissuades attempts to dissassemble- things like special screws (Torx); again, warranted when the device innards were generally something that wasn’t user-servicable to the typical enthusiast, but now it’s just a artificial barrier to make the machines seem less user-servicable than they are. And, more to the point, the fact is that they simply fail more often now, and it seems like it would be in the company’s best interest to make them more user-servicable since that would mean fewer warranty repairs. (Obviously they can keep their old “take it apart and void the Warranty” thing.

974 total views, no views today

Posted By: BC_Programming
Last Edit: 13 Dec 2011 @ 10:04 AM

EmailPermalinkComments (0)
Tags

 Last 50 Posts
 Back
Change Theme...
  • Users » 856
  • Posts/Pages » 191
  • Comments » 67
Change Theme...
  • VoidVoid « Default
  • LifeLife
  • EarthEarth
  • WindWind
  • WaterWater
  • FireFire
  • LightLight

PP



    No Child Pages.

Windows optimization tips



    No Child Pages.