20 Jan 2012 @ 5:52 AM 

I see this question semi-frequently on forums. It’s a reasonable question on the surface. However, the fact is that there is no “Best language for a beginner”. Truly, the very idea that a given computer language could be “better for beginners” than another is downright assinine.

Consider human languages. A person learns language over time, and there isn’t any more difficulty for different languages; the skill develops through practice. Chinese isn’t “harder” to learn than English, for example; but it is harder to learn when you know english.

So how does this extend into computer languages?

Well, the first computer language you learn won’t matter, just as the first Human language you learned doesn’t really matter.For Human languages, the big thing you are learning is how to express yourself to others. For Programming languages, the big thing you are learning is how to express yourself- to the computer.

Once you learn one imperative programming language, the others are easier to learn; same way with human languages. Once you know one germanic language, the others are typically easier to learn.

Of course, it’s harder to learn Chinese or Japanese when you are used to a latin alphabet; for programming languages, this “barrier” is typically found between imperative, Declarative, and functional languages. of course the actual difficulty is not quite at the same level; but functional languages are fundamentally different in many ways from imperative languages, enough that trying to take what you learned via programming in an imperative language and applying it to a functional one can do more harm than good to your learning process, just as trying to apply your understanding of English will harm attempts to learn most non-germanic languages, trying to apply an understanding of Chinese/Japanese/etc to learning English will harm attempts to learn it as well.

Additionally, one issue I find is that many people will say that “language X is easy to learn because it’s like plain english”. Which is a flawed perspective, since that brings with it a lot of language baggage; english is designed for communicating between people; a programming language is designed for communicating between a person and a computer (or rather, the interpreter/compiler which makes it into something the computer understands, but let’s not cloud the issue). So you end up with a psuedo-english language that tries to be declarative but at the same time isn’t english; SQL, for example, is english-like, but I’d be hard-pressed to say that “Select * from Customers Where CX>6″ is “plain english”. It’s easy to understand, but it’s restrictive; after all, in english the same concept could be expressed as “choose all fields from the customers table where the record in question has a CX field greater than 6″. But that is far from valid SQL, which has a far more restrictive grammar and syntax. (if it didn’t, parsing it would be a nightmare).

This is how I’ve always felt about it. What is important isn’t what specific language you learn but that you learn the concepts involved; for imperative languages this would be references, functions, recursion, variable allocations,object mutability, (and for OOP languages the various OOP concepts, such as polymorphism, aggregation, composition, delegation, etc). Functional languages, (and some imperative languages that integrate functional features) it’s things like lambdas, recursive definitions,Higher-order/First-class functions (Functions that take other functions as arguments), pure functions, strict versus non-strict evaluation, catamorphism, etc.

Once you learn the concepts, you can use pretty much learn any language with minimal effort, you just need to learn the syntax.(unless you are jumping across a declarative/imperative/functional divide, in which case you also need to learn and relearn other concepts as well). Overall, however, Learning a syntax is easy; the first programming language you learn is hardest no matter which one you choose simply because you have to learn the syntax at the same time you are learning all sorts of “new” concepts. It doesn’t matter what language you choose; it will always be “harder” to learn than later languages for you.

 12 Jan 2012 @ 3:11 PM 

In some of my recent posts, I’ve covered the topic of accessing and parsing an INI file for configuration data in a C# Application.

Some may wonder why. After all; the “norm” for C# and .NET applications is to use XML files for configuration information, isn’t it? Well, yes. But to be honest, XML files are a fucking pain in the ass. They aren’t human readable to your average person the same way an INI file is, and getting/setting values is tedious. Primarily, the reason I use INI files is that they are:

  1. Human Readable: Anybody can understand the basic structure of the sections and Name=Value syntax.
  2. Accessible: You don’t need a special editor
  3. Portable: since the entire thing is interpreted using Managed code, it will act the same on any platform (Mono or the MS CLR).

Mostly, I feel that XML, and in many ways other configuration options, are more or less driven by fad. Another option for configuration settings on Windows is the Registry, which is in fact often the recommended method; but this is anything but accessible to the user. Would you rather guide a user to edit a INI file or to fiddle with registry settings?

With that said, INI Files do have their own issues. For example, their data is typically typeless; or, more precisely, the Values are all strings. Whereas using a .NET XML Serializer, for example, you could easily(relatively speaking) serialize and deserialize a special configuration class to and from an XML file and preserve it’s format, with my INI file class there will typically be some work to parse the values.

It was with the idea of turning my string-only INIFile configuration settings into something that can be used for nearly any type that I created the INItemValueExtensions class, which is nothing more than a static class that provides some extension methods for the INIDataItem class. I covered this in my previous post.

The prototypes for the two static functions are:

  1.  
  2. public static T GetValue<T>(this INIDataItem dataitem, T DefaultValue);
  3.  
  4. //and
  5.  
  6. public static void SetValue<T>(this INIDataItem dataitem, T newvalue);
  7.  
  8.  

How would one use these extension methods? Well, here’s an Example:

  1.  
  2. public static void main(String[] args)
  3. {
  4.     INIFile loadini = new INIFile("D:\\testini.ini");
  5.     loadini["Dates"]["TestDate"].SetValue(DateTime.Now);
  6.     DateTime readvalue = loadini["Dates"]["TestDate"].GetValue<datetime>();
  7.    
  8. }
  9. </datetime>

Woah, hold the phone! What’s going on here? We’re loading DateTime values directly from the INI File? How does that work?

All the “magic” happens in the getValue generic extension method. The first thing the routine does is check to see if the Type Parameter has a static TryParse() method; if it implements ISerializable and have a TryParse method, than the routine will read the string from the INI file, decode it via Base64, and throw it in a MemoryStream, and then try to deserialize the Object Graph for a Type T using that stream.

If it does implement a TryParse() routine, (like, for example, DateTime) it doesn’t try quite as hard. It takes the string from the INI file and hands it to the Type’s TryParse() routine, and then returns what that gives back. Naturally, the inverse function (setValue) does something somewhat opposite; it checks the Base64 logic, and if so it sets the value of the item to the Base64 encoded value of the serialized object. Otherwise, it just uses toString().

This typically works, particularly with DateTime, because usually ToString() is the inverse of TryParse(). In the case of DateTime, this has a few edge cases with regards to locale, but usually it works quite well. And more importantly, the introduction of allowing any object that implements ISerializable to simply be thrown as an INI value via a Base64 encoded string is useful too, although with large objects it’s probably not a good idea for obvious reasons.

But… I still want to access other settings!

Of Course, an INIFile is only one of any number of ways to store/retrieve configuration settings. And while they don’t typically lend themselves to the same syntax provided by the INIFile class, it would be useful to have some sort of common denominator that can handle it all. That was the original intent of the relatively unassuming ISettingsStorage interface:

  1.  
  2.    public interface ISettingsStorage
  3.     {
  4.         void Save();
  5.         void Load();
  6.         void AddValue(String Category, String ValueName, String Value);
  7.         String GetValue(String Category, String ValueName);
  8.     }
  9.  

This uses a concept known as a “category” which is pretty much the same idea as an INI File section. What makes it different is that, for implementors that use other storage mechanisms, it could have additional meaning; for example, a fictitious XML implementation of ISettingsStorage could use the “Category” string as an XPath to an element; and the Value could be stored/retrieved as a Attribute. a Registry implementation might use it as a Registry path, and so on.

The problem is, even though the INIFile class implements this interface, it’s too basic, and doesn’t provide nearly the syntactic cleanliness that just using the INIFile does. Stemming from that, and because I wanted to try to get a way to store settings directly in a DB, I introduced two events to the INIFile class; one that fires when a Value is retrieved, and one when a value is saved. This way, the event could be hooked and the value saved elsewhere, If desired. Now, to be fair, this is mostly a shortcoming of my interface definition; as you can see above, there is no way to, for example, inspect category or Value names. I toyed with the idea of adding a “psuedo” category/value combination that would return a delimited string of category names, but that felt extremely silly. The creation of a generic interface- or abstract class- that provides all the conveniences I currently enjoy using my INIFile class but allowing me to also use XML, Registry, or nearly any other persistent storage for settings will be a long term goal. For now, I’m content with accessing INI files and having a unclean event to hack in my own behaviour.

My first test of the above feature- whereby it allows values to be TryParse’d and ToString’d back and forth from a given type on the fly- was the creation of a FormPositionSaver class.

The proper way to save and restore a window’s position on Windows is using the GetWindowPlacement() and SetWindowPlacement() API Functions. These use a structure, named, quite aptly, “WINDOWPLACEMENT” to retrieve and set the window position and various attributes. Therefore, our first task is to create the proper P/Invoke’s for these functions:

  1.  
  2.  [DllImport("user32.dll")]
  3.         private static extern int OffsetRect(ref RECT lpRect, int x, int y);
  4.  
  5. [DllImport("user32.dll")]
  6.         private static extern int GetWindowPlacement(IntPtr hwnd, ref WINDOWPLACEMENT lpwndpl);
  7.  
  8. [DllImport("user32.dll")]
  9.         private static extern int SetWindowPlacement(IntPtr hwnd, ref WINDOWPLACEMENT lpwndpl);
  10.  

I also include OffsetRect(), but I’ll get to that in a bit. Now the “big one” is the definition of the WINDOWPLACEMENT structure and it’s various aggregate structures. Why? well, in the interest of leveraging the INIFile’s static extensions, Why not define a static TryParse() and a toString() method on the structure that can set and retrieve the member values:

  1.  
  2.  
  3.         [StructLayout(LayoutKind.Sequential)]
  4.         private struct POINTAPI
  5.         {
  6.             internal int x;
  7.             internal int y;
  8.  
  9.  
  10.             public POINTAPI(int px, int py)
  11.             {
  12.                 x = px;
  13.                 y = py;
  14.             }
  15.  
  16.             public static void TryParse(String parseit, out POINTAPI result)
  17.             {
  18.                 //format: (X,Y)
  19.                 //strip out parens.
  20.                 String[] parsed = parseit.Replace("(", "").Replace(")", "").Split(new char[] {‘,’});
  21.                 int kx = int.Parse(parsed[0]);
  22.                 int ky = int.Parse(parsed[1]);
  23.  
  24.  
  25.                 result = new POINTAPI(kx, ky);
  26.             }
  27.  
  28.             public override string ToString()
  29.             {
  30.                 return "(" + x + "," + y + ")";
  31.             }
  32.         }
  33.  
  34.         [StructLayout(LayoutKind.Sequential)]
  35.         private struct RECT
  36.         {
  37.             internal int Left;
  38.             internal int Top;
  39.             internal int Right;
  40.             internal int Bottom;
  41.  
  42.             public override string ToString()
  43.             {
  44.                 return "{" + new POINTAPI(Left, Top).ToString() + "-" + new POINTAPI(Right, Bottom).ToString() + "}";
  45.             }
  46.  
  47.             public RECT(int pLeft, int pTop, int pRight, int pBottom)
  48.             {
  49.                 Left = pLeft;
  50.                 Top = pTop;
  51.                 Right = pRight;
  52.                 Bottom = pBottom;
  53.             }
  54.  
  55.             public static void TryParse(String parsestr, out RECT result)
  56.             {
  57.                 //strip out braces…
  58.                 parsestr = parsestr.Replace("{", "").Replace("}", "");
  59.                 //split at ")-("…
  60.                 String[] Pointstrings = parsestr.Split(new string[] {")-("}, StringSplitOptions.RemoveEmptyEntries);
  61.                 POINTAPI firstpoint, secondpoint;
  62.                 //parse the resulting values. re-add the parens that were removed by the split.
  63.                 POINTAPI.TryParse(Pointstrings[0] + ")", out firstpoint);
  64.                 POINTAPI.TryParse("(" + Pointstrings[1], out secondpoint);
  65.  
  66.  
  67.                 result = new RECT(firstpoint.y, firstpoint.y, secondpoint.x, secondpoint.y);
  68.             }
  69.         }
  70.  
  71.  
  72.  [StructLayout(LayoutKind.Sequential)]
  73.         private struct WINDOWPLACEMENT
  74.         {
  75.             internal int Length;
  76.             internal int flags;
  77.             internal int showCmd;
  78.             internal POINTAPI ptMinPosition;
  79.             internal POINTAPI ptMaxPosition;
  80.             internal RECT rcNormalPosition;
  81.  
  82.             public override string ToString()
  83.             {
  84.                 return String.Join(",", new string[]
  85.                                             {
  86.                                                 flags.ToString(), showCmd.ToString(),
  87.                                                 ptMinPosition.x.ToString(), ptMinPosition.y.ToString(),
  88.                                                 ptMaxPosition.x.ToString(), ptMaxPosition.y.ToString(),
  89.                                                 rcNormalPosition.Left.ToString(), rcNormalPosition.Top.ToString(),
  90.                                                 rcNormalPosition.Right.ToString(), rcNormalPosition.Bottom.ToString()
  91.                                             });
  92.             }
  93.  
  94.             //parsed a string into a WINDOWPLACEMENT structure.
  95.             public static bool TryParse(String parseme, out WINDOWPLACEMENT result)
  96.             {
  97.                 try
  98.                 {
  99.                     String[] splitvalues = parseme.Split(‘,’);
  100.                     int[] parsedvalues = new int[splitvalues.Length];
  101.  
  102.  
  103.                     for (int i = 0; i < parsedvalues.Length; i++)
  104.                     {
  105.                         int.TryParse(splitvalues[i], out parsedvalues[i]);
  106.                     }
  107.  
  108.                     result = new WINDOWPLACEMENT
  109.                                  {
  110.                                      flags = parsedvalues[0],
  111.                                      showCmd = parsedvalues[1],
  112.                                      ptMinPosition = new POINTAPI(parsedvalues[2], parsedvalues[3]),
  113.                                      ptMaxPosition = new POINTAPI(parsedvalues[4], parsedvalues[5]),
  114.                                      rcNormalPosition =
  115.                                          new RECT(parsedvalues[6], parsedvalues[7], parsedvalues[8], parsedvalues[9])
  116.                                  };
  117.  
  118.                     return true;
  119.                 }
  120.                 catch
  121.                 {
  122.                     result = new WINDOWPLACEMENT();
  123.                     return false;
  124.                 }
  125.             }
  126.         }
  127.  
  128.  

WHEW! that’s quite a bit of code for a structure definition, but we’ll make up for it with the brevity of the actual FormPositionSaver class itself. First, my design goal with this class was to make it basically do all the heavy lifting; it hooks both the Load and Unload event, and saves to and from a given INIFile Object in those events. Since the application I was working on at the time didn’t actually get a Valid INI object until during it’s main form’s Load event, and since there is no way to say “Invoke this event first no matter what” I also added a way for it to be told that hooking the load event would be pointless since it already occured, at which point it will not hook the event and instead set the form position immediately. Values are stored

  1.  
  2. public class FormPositionSaver
  3. {
  4.  
  5.   private Form FormObject = null;
  6.         private INIFile Configuration = null;
  7.         private static readonly String usesectionName = "WindowPositions";
  8.  
  9.  
  10.         /// <summary>
  11.         /// Create the FormPositionSaver
  12.         /// </summary>
  13.         /// <param name="FormObj">Form to deal with</param>
  14.         /// <param name="configfile">INIFile to load and save</param>
  15.         /// <param name="alreadyloaded">whether the Load event has fired. If true, will try to set the form position immediately. otherwise, it hooks the Load event and waits.</param>
  16.         public FormPositionSaver(Form FormObj, INIFile configfile, bool alreadyloaded)
  17.         {
  18.             Configuration = configfile;
  19.             FormObject = FormObj;
  20.  
  21.             FormObject.FormClosed += new FormClosedEventHandler(FormObject_FormClosed);
  22.  
  23.  
  24.             if (!alreadyloaded)
  25.                 FormObject.Load += new EventHandler(FormObject_Load);
  26.             else
  27.             {
  28.                 FormObject_Load(FormObject, new EventArgs());
  29.             }
  30.         }
  31.  
  32.         //save the placement...
  33.         private void FormObject_FormClosed(object sender, FormClosedEventArgs e)
  34.         {
  35.             //save placement.
  36.             //all the "tough work" is handled above, and by the INIDataItem Extension methods. Here we
  37.             //can simply use SetValue<> and set the value. Nice and clean.
  38.             WINDOWPLACEMENT grabplacement = new WINDOWPLACEMENT();
  39.             GetWindowPlacement(FormObject.Handle, ref grabplacement);
  40.             Configuration[usesectionName][FormObject.Name].SetValue(grabplacement);
  41.         }
  42.  
  43.         //Load event: load the form placement, if present, from the INI file we were given in our constructor.
  44.         private void FormObject_Load(object sender, EventArgs e)
  45.         {
  46.             WINDOWPLACEMENT currplacement = new WINDOWPLACEMENT();
  47.             GetWindowPlacement(FormObject.Handle, ref currplacement);
  48.                 //default is wherever it is now if there is a parse problem.
  49.  
  50.             WINDOWPLACEMENT getplacement =
  51.                 Configuration[usesectionName][FormObject.Name].GetValue(currplacement);
  52.  
  53.             //check for previous instances, and offset if there are.
  54.             String thisproc = Process.GetCurrentProcess().ProcessName;
  55.             Process[] existing = Process.GetProcessesByName(thisproc);
  56.             if (existing.Length > 1)
  57.             {
  58.                 //more than one, so offset...
  59.                 OffsetRect(ref getplacement.rcNormalPosition, 16*existing.Length, 16*existing.Length);
  60.             }
  61.  
  62.  
  63.             SetWindowPlacement(FormObject.Handle, ref getplacement);
  64.  
  65.             //load placement...
  66.         }
  67.     }
  68.  

Alright, so maybe I lied a bit. It's not super short. Although a lot of it is comments. Some might note that I only sporadically add doc comments, even though I ought to be adding them everywhere. Well, sue me. I just add them when I feel like it. When I'm concentrating on function, I'm not one to give creedence to form.

This is where I explain OffsetRect(). Basically, if your application is run twice, and you load the form position twice, the second form will open over the first one, and the screen will look pretty much the same. So we detect previous instances and offset by an amount to make it's position different from any previous instances as necessary. That's pretty much the only purpose of OffsetRect.

I have packaged the current versions of cINIFile.cs and the new FormPositionSaver.cs in a zip file, it can be downloaded from here.

Posted By: BC_Programming
Last Edit: 12 Jan 2012 @ 03:11 PM

EmailPermalinkComments (0)
Tags
 01 Jan 2012 @ 5:20 PM 

HAHA! How’s that for a clever title?

Oh… well… ahem… nevermind.

As a avid user of my own INIFile class, which I first write about- at least it’s C# implementation- in my parsing INI files posting, I am always looking for ways to improve it’s usage make it more “accessible”.

Recently, I have been tasked (by way of my new title of “freelance consultant”) with creating several LOB (Line of Business) Type applications. Applications, naturally, have a tendency to lend their implementations to the creation and reading of settings. Being something of a fan of the simplicity of INI Files, I chose to use my INIFile class in the application. It works well, however, I have noticed that I have a lot of duplicate code. More specifically, I typically have to implement a “wrapper” class, which manages configuration information and reads/writes values to and from the INIFile as its own properties are accessed. For example:

  1. public bool PopulateUserOrderDropdown
  2. {
  3.    get {
  4.     bool tparse;
  5.     if(bool.TryParse(OurINI["Admin.Settings"]["PopulateUserOrderDropDown","false"].Value,out tparse)
  6.         return tparse;
  7.     return false;
  8.    }
  9.    set { OurINI["Admin.Settings"]["PopulateUserOrderDropDown"].Value;}
  10. }

Nothing too dreadful, but imagine having nearly the exact same thing repeated a number of times! The code is repeated and as Larry Wall says, one of the traits of a good programmer is sloth. I don’t like having to write this same code over and over again! The INIFile is supposed to make it easy!

The trouble here stems from the fact that the INIFile values are only strings; and typically, many settings are represented in the application itself as integers and booleans, dates, and so forth. My first attempt to mitigate the clutter was a static method, which I called xParse:

  1. public static class boolEx
  2. {
  3.     public static bool xParse(String Value, bool Default)
  4.     {
  5.         bool parseresult;
  6.         if(bool.TryParse(Value,out parseresult))
  7.             return parseresult;
  8.         else
  9.             return Default;
  10.  
  11.     }
  12. }

relatively straightforward- basically it’s a shell of what I had repeated over and over again. This mitigated the issue somewhat, so my properties in the wrapper looked like this:

  1. public bool PopulateUserOrderDropdown
  2. {
  3.    get { return boolEx.xParse(OurINI["Admin.Settings"]["PopulateUserOrderDropDown", "false"].Value); }
  4.    set { OurINI["Admin.Settings"]["PopulateUserOrderDropDown"].Value;}
  5. }

much more managable, but still, could we not make this more concise? My first thought, was that perhaps I could eliminate the necessity of having the wrapper at all; I recalled two interfaces from my old COM programming days, specifically, IDispatch and IDispatchEx. Surely, I could do something similar?

Unfortunately, the interfaces are for COM, and C# doesn’t have dynamics until Version 4.

So, I fired up Visual Studio 2010 express to see if I couldn’t add the dynamic language constructs to the INIFile class; additionally, since I still need to work with .NET 3.5, I’ll add the new code as a conditional compilation.

The first step was deciding exactly what I wanted to happen. Imagine code like this:

  1. INIFile useINI = new INIFile("settings.ini");
  2. String ConnectionString = (String)useINI.General.ConnectionString;

The holy grail of the INIFile simplicity! Naturally, the .NET framework does provide the facility with which to add this functionality, as part of the System.Dynamic namespace.

The first step was deciding on the method by which to conditional compile. Since projects copy the source of a file to your project folder when you add them, it seemed reasonable to simply add it as a #define right inside the INIFile class itself.

#define CS4

And now, I just need to enclose all my new happy stuff in a conditional directive, and I’ll get the best of both worlds- C# 4.0 consumers who keep the #define will be able to use the suave new feature, and older consumers will still be able to work without ripping apart the classes. The code to add this was surprisingly simple; as it stands now the longest method (An implementation of TryDeleteMember, which is never called from C#/VB.NET consumers, so is excessive for my usage). First, obviously we enclose the import statement in the conditional compile; the class headers are conditionally compiled as well, only deriving from DynamicObject with CS4 set.

The core of the new functionality is in the overrides to the Dynamic Object’s TryGetMember.

For the INISection:

  1. public override bool TryGetMember(GetMemberBinder binder,out object result)
  2. {
  3.     result = this[binder.Name];
  4.     return true;
  5. }

And for the INIFile…

  1. public override bool TryGetMember(GetMemberBinder binder,out object result)
  2. {
  3.     result = this[binder.Name];
  4.     return true;
  5. }

Exactly the same, in fact. This works because of the indexer I added; the indexer will add the item if it doesn’t exist and return the new value, so even if the member name doesn’t exist, the INIFile will simply have that section added.

That’s for the retrieval of erements; to allow the assignment to them in the same fashion, we need to override TrySetMember(). In my case, this was a bit more involved, for flexibility purposes.

For example, code like INIFile.MainSection=”hello” should work, and change the name of the section. And why allow things like assignments from a Dictionary<String, String>, or maybe even a list (assigning a numbered id to set values)? And of course allow setting the Value directly, which will likely use the indexer much as I did for the TryGet… Implementations.

  1.        public override bool TrySetMember(SetMemberBinder binder, object value)
  2.         {
  3.  
  4.             //if it is a dataitem, set it directly.
  5.             if (value is INIDataItem)
  6.             {
  7.                 this[binder.Name] = (INIDataItem)value;
  8.                 return true;
  9.             }
  10.             else if (value is Tuple<, Object>)
  11.             {
  12.                 Tuple<, Object> theTuple = (Tuple<, Object>)value;
  13.                 INIDataItem getitem = this[binder.Name];
  14.                 getitem.Name = theTuple.Item1;
  15.                 getitem.Value = theTuple.Item2.ToString();
  16.                 return true;
  17.  
  18.             }
  19.             else if (value is Tuple<, String>)
  20.             {
  21.                 Tuple<, Object> theTuple = (Tuple<, Object>)value;
  22.                 INIDataItem getitem = this[binder.Name];
  23.                 getitem.Name = theTuple.Item1;
  24.                 getitem.Value = theTuple.Item2.ToString();
  25.                 return true;
  26.             }
  27.  
  28.             else if (value is KeyValuePair<, Object>)
  29.             {
  30.  
  31.                 //Allow a KeyValuePair<,Object> to be passed to set Name and Value.
  32.                 KeyValuePair<, Object> castedval = (KeyValuePair<, Object>)value;
  33.                 INIDataItem getitem = this[binder.Name];
  34.                 getitem.Name = castedval.Key;
  35.                 getitem.Value = castedval.Value.ToString();
  36.  
  37.                 return true;
  38.             }
  39.             else if (value is KeyValuePair<, String>)
  40.             {
  41.                 //Allow a KeyValuePair<,String> to be passed to set Name and Value.
  42.                 KeyValuePair<, String> castedval = (KeyValuePair<, String>)value;
  43.                 INIDataItem getitem = this[binder.Name];
  44.                 getitem.Name = castedval.Key;
  45.                 getitem.Value = castedval.Value;
  46.  
  47.                 return true;
  48.  
  49.             }
  50.  
  51.             else
  52.             {
  53.                 this[binder.Name].Value = value.ToString();
  54.                 return true;
  55.             }
  56.         }

setting the Value should be equally flexible; since we can, why not?
for example, why not make the following “legal”?

  1. INIFile.Section.Value="newvalue";
  2. INIFile.Section.Value=DateTime.Now;
  3. INIFile.Section.Value=Tuple.Create("NewName","Chicken");
  4. INIFile.Section.Value=Tuple.Create("NewName",DateTime.Now);

The first example sets the Value to a string, the second sets it to a DateTime that is silently casted to a String (using toString(), and the last two use the new C# 4.0 tuples, to set both the name of the value and the value simultaneously.

A more elegant solution would be to add this code to the Indexer, and merely call the indexer with the name and the value and return true if no exception occurs and false otherwise. However, I’m reluctant to go that route since some of the types are C# 4 types (Tuples).

  1.        public override bool TrySetMember(SetMemberBinder binder, object value)
  2.         {
  3.  
  4.             if (value is String)
  5.             {
  6.                 this[binder.Name].Name = (String)value;
  7.                 return true;
  8.  
  9.             }
  10.             else if (value is List<INIItem>)
  11.             {
  12.                 INISection getsection = this[binder.Name];
  13.                 getsection.INIItems = (List<INIItem>)value;
  14.                 return true;
  15.  
  16.             }
  17.             else
  18.             {
  19.                 return false;
  20.  
  21.             }
  22.  
  23.         }

So Now, I’ve got an INI File implementation that supports Dynamic invocation. Well, that’s great… except that the application I first found it clumsy in is using .NET 3.5, so I can’t use the dynamic features. Back at square one.

In C# 2008/3, we might not be able to leverage the power of dynamics, but we do have generics and Extension methods at our disposal. a feasible alternative could be to add a extension method to the INIDataItem class that has a generic type parameter that it will attempt to convert it’s string Value into. First, using ChangeType, second, it can try to invoke a static TryParse on the given Type to parse the “value” string. And if none of that works, it can return a passed in default. This is still more verbose than the dynamic solution, but it has two distinct advantages- first, it’s type-safe, so you get all the intellisense goodness, and second, it’s still shorter than the alternative.

Here is the code, which can be found in the cINIFile.cs file attached to this posting as well.

  1.    public static class INItemValueExtensions
  2.     {
  3.         //extensions for INIDataItem
  4.  
  5.         //normally, INIDataItem is a Name/Value Pair; More Specifically, because of the way INI files are, they are
  6.         //naturally typeless. However, most configuration options are mapped to a different type by the application.
  7.         //and I’ve found it to be a gigantic pain to have to write the same TryParse() handling code over and over.
  8.         //so I added these handy extensions to the INIDataItem class, which provide some functions for setting.
  9.         //I keep them out of the main code simply because that way it doesn’t clutter it up. It’s already cluttered enough as-is.
  10.  
  11.         /// <summary>
  12.         /// Attempts to use Convert.ChangeType() to change the Value of this INIDataItem to the specified type parameter.
  13.         /// If this fails, it will attempt to call a static "TryParse(String, out T)" method on the generic type parameter.
  14.         /// If THAT fails, it will return the passed in DefaultValue parameter.
  15.         /// </summary>
  16.         /// <typeparam name="T">Parameter Type to retrieve and act on in Static context.</typeparam>
  17.         /// <param name="dataitem">INIDataItem instance whose value is to be parsed to the given type.</param>
  18.         /// <param name="DefaultValue">Default value to return</param>
  19.         /// <returns>Result of the parse/Conversion, or the passed in DefaultValue</returns>
  20.         public static T GetValue<T>(this INIDataItem dataitem, T DefaultValue)
  21.         {
  22.  
  23.             //Generic method, attempts to call a static "TryParse" argument on the given class type, passing in the dataitem’s value.
  24.             try
  25.             {
  26.                 return (T)Convert.ChangeType(dataitem.Value, typeof(T));
  27.             }
  28.             catch (InvalidCastException ece)
  29.             {
  30.                 //attempt to call TryParse. on the static class type.
  31.                 //TryParse(String, out T)
  32.  
  33.                 Type usetype = typeof(T);
  34.                 T result = default(T);
  35.                 Object[] passparams = new object[] { dataitem.Value, result };
  36.                 try
  37.                 {
  38.                     bool tpresult = (bool)usetype.InvokeMember("TryParse", BindingFlags.Static, null, null, passparams);
  39.                     if (tpresult)
  40.                     {
  41.                         //tryparse succeeded!
  42.                         return (T)passparams[1]; //second index was out parameter…
  43.                     }
  44.                 }
  45.                 catch (Exception xx)
  46.                 {
  47.                     //curses…
  48.                     return DefaultValue;
  49.  
  50.                 }
  51.  
  52.             }
  53.             return DefaultValue;
  54.  
  55.         }
  56.         /// <summary>
  57.         /// Logical inverse of the getValue routine… a bit faster to implement…
  58.         /// </summary>
  59.         /// <typeparam name="T"></typeparam>
  60.         /// <param name="dataitem"></param>
  61.         /// <param name="newvalue"></param>
  62.  
  63.         public static void setValue<T>(this INIDataItem dataitem, T newvalue)
  64.         {
  65.             dataitem.Value = newvalue.ToString();
  66.  
  67.         }
  68.  
  69.         private static void GetTypeDefault<T>(out T result)
  70.         {
  71.             Type tt = typeof(T);
  72.  
  73.             //basic idea: call default, empty constructor using reflection.
  74.             ConstructorInfo defaultconstructor = tt.GetConstructor(new Type[] { });
  75.  
  76.             result = (T)defaultconstructor.Invoke(null);
  77.  
  78.         }
  79.  
  80.     }
  81.  
  82.  
  83.  

And there you have it, a bunch of awesome additions. INI files are often thought of as deprecated, but that’s only the INIFile functions. This class was designed because working with the registry makes it difficult to test properly, and because JSON,YAML, and many other formats are excessively complicated. when you just need a few basic settings, all you need is the clean, simple format of a INI file. And now, with these additions, code for reading from those INI files is clean and simple as well!

The Source- cINIFile.cs

Posted By: BC_Programming
Last Edit: 01 Jan 2012 @ 05:27 PM

EmailPermalinkComments (0)
Tags
 25 Dec 2011 @ 2:05 PM 

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

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.

Posted By: BC_Programming
Last Edit: 25 Dec 2011 @ 02:05 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.

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 Downloadspage) 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.

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

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

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.

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

EmailPermalinkComments (0)
Tags
 16 Nov 2011 @ 7:12 PM 

So, as mentioned in the previous post, I added a “sort” of scriptability to BASeBlock.

I made some tweaks, and refactored the code so it was a bit more abstracted; the original implementation was directly in the MultiTypeManager, but that didn’t really have anything to do with it, so I tweaked some of the parameters to a few methods there, added a new class with the static routines required for the needed functionality, etc. I also made it so that a “BASeBlock Script Group file” (.bbsg extension) could be used to both compile a set of files into an assembly, as well as include various other assemblies as required. Future additions will probably include the ability for each assembly to define a sort of “main” method, which can be called when the assembly is initialized.

However, once again, Serialization was the constant thorn in my side. I was able to mess about with a custom block written in a .cs script, and it even saved properly.

But the game encountered an exception when I tried to open that LevelSet; I forget the specifics, something to the tune of “failed to find assembly”  type of error. What could I do?

 SerializationBinder

What this basically meant was I was going to have to learn even more about the Serialization structure of .NET. Specifically, SerializationBinder’s. The concept was actually quite simple. You basically just derive a type from SerializationBinder, and use that as the .Binder property on a IFormatter class, overriding one method seems to be enough for the most part:

 

  1. Type BindToType(string assemblyName, string typeName)

 

Simple! it gives you an assembly Name, a Type Name, and you simply return the appropriate type. The reason the default implementation wasn’t working was certainly as a result of the assembly being loaded dynamically, since it wasn’t [i]really[/i] being referenced by the BASeBlock assembly, so the default implementation didn’t find the “plugin” class assembly or the appropriate type, so threw an exception.

The trick here is not to enumerate the referenced assemblies, but rather to use all loaded assemblies in the current AppDomain. The general consensus with regards to using CodeDOM and compiling things like this is to compile them to their own AppDomain; however, since the assemblies were being kept “alive” for the duration of the application, that wasn’t necessary, and in this case would have complicated things. Well, it would have complicated things more than they already were.

The “AssemblyName” parameter, however, was more akin to the FullName property of the System.Reflection.Assembly; for example, BASeBlock’s assembly would (for the current version) be passed in as “BASeBlock, Version=1.4.0.0, Culture=neutral, PublicKeyToken=null”. Since we are only interested in the actual name of the assembly, we can simply grab everything up to the first comma.

Armed with the Assembly’s base name, we can start enumerating all the loaded Assemblies and looking for a match:

  1. public override Type BindToType(string assemblyName, string typeName)
  2.             {
  3.  
  4.                 try
  5.                 {
  6.                     string BaseAssemblyName = assemblyName.Split(‘,’)[0];
  7.                     Assembly[] Assemblies = AppDomain.CurrentDomain.GetAssemblies();
  8.                     foreach (Assembly loopassembly in Assemblies)
  9.                     {
  10.                         if (loopassembly.FullName.Split(‘,’)[0].Equals(BaseAssemblyName,StringComparison.OrdinalIgnoreCase))
  11.                         {
  12.                             return loopassembly.GetType(typeName);
  13.  
  14.                         }
  15.                     }
  16.                 }
  17.                 catch (System.Exception exception)
  18.                 {
  19.                     throw exception;
  20.                 } return null;
  21.             }

Then, we compare the Assembly.FullName.Split(‘,’)[0] (the text before the first comma) to the modified string, BaseAssemblyName that was changed in the same manner. I decided to go string insensitive for no reason; mostly because the assembly names for the scripts are formed from the filenames and I wouldn’t want filename capitalization to prevent a script from serializing/deserializing properly. If we find a matching assembly, we return the result of a GetType() call to that assembly with the same typename passed in as a parameter to the method. The Formatter will than attempt to deserialize the data it has to that type as needed.

There are a few issues with this- for one thing, it doesn’t work with Generic types. At least, I assume it doesn’t; I assume I would need some special code to get the appropriate Type for a generic type given type arguments. I’ll cross that bridge when I come to it.

Right now, I’ve not actually tested this extensively. My main concern would be to test that it can serialize in one session, and deserialize in another. This concern is based on the fact that the two assemblies would in that case be literally distinct- in that it would have been compiled on two occasions. Assuming the Binder is enough to convince the serializer it can deserialize something, there shouldn’t be any issues. Once I decide to figure out how to add Generics support to this Binder, I’ll definitely write about it here.

Posted By: BC_Programming
Last Edit: 16 Nov 2011 @ 07:13 PM

EmailPermalinkComments (0)
Tags
 13 Nov 2011 @ 1:46 PM 

When I first started developing BASeBlock, the basic idea was just to get a feel for how Animation, rendering, and other concepts were dealt with in C#, as opposed to the aging language I was familiar with VB6. Additionally, I wanted to make use of Threading. After getting through the initial hurdles and issues related to the type of code I would need to write with BCDodger, I had a good idea the type of things I needed for BASeBlock.

 

BASeBlock’s actual Game information is, not surprisingly, kept inside a instance of a “BCBlockGameState” object. There are a few folly’s in my design, whereby some elements that would probably be best placed as members of BCBlockGameState are in fact members of the main form, but everything works well as it is, and if necessary objects can get a reference to the form itself using the BCBlockGameState ClientObject property, which is a interface that defines the type of stuff that is more implementation specific than ought to be in the State instance. Rendering is managed entirely within the Paint event of the main form’s PictureBox- (and likewise for the Editor). Rendering and the actual GameProc() routine (that makes things happen) are similar; they both iterate through the various objects in the game and call methods. The GameProc() routine animates balls, GameObjects, Particles, and AnimatedBlocks; the Paint event basically iterates through all those objects and get’s them to paint on a bitmap which is them blitted to the PictureBox when necessary. Of course having these two activities on separate threads meant  that I tripped on a few things while I was getting started- not locking this variable, trying to call methods of the form from within the game loop, and other similar contention issues. All of those have been resolved, to the best of my knowledge.

Another goal while working on BASeBlock was to make it easier for the creation of new blocks. The way the older Poing game (VB6) did it was to only have a single Block object, which had an Enumeration that determined what kind of block it really was. This had the issue whereby the Block had a lot of fields that were only applicable with certain block types, and weren’t applicable at all otherwise. This was a primarily a result of the fact that VB6 isn’t really an Object Oriented Language, but rather an Object-Based Language; while you can fake Implementation Inheritance using the Implements keyword and an aggregate class, it’s a lot of boilerplate that I couldn’t be bothered with for little gain. With C#, and more importantly proper OOP constructs, I could try my hand at a more reasonable Object heirarchy.

Collections

By far one of the most annoying things to deal with in VB6 is collections. they are finicky, and more importantly, the built-in Collection class only deals with Objects, so I would have to cast variables all over the place, and casting in VB6 means actually declaring another variable of that type and using Set to attempt the assignment to perform the “cast”. The only “workaround” is to create a entirely new class that uses a Collection internally but exposes the members of that Collection as the more specific type, as well as having Strongly-Typed parameters for Addition of blocks and removal. But the creation of these classes:

  • Is a gigantic pain in the ass. They are almost all exactly the same, differing only in the Type used.
  • Clouds the namespace. Poing has a Levels.cls class, a Blocks.cls class, a Balls.cls class, and a few others, all for the sole purpose of representing the exact same data structure for different objects.
  • Is a gigantic pain in the ass. I already said that, but I just wanted to make sure I was clear about that.
  • While you can use For…Each on the collection, it’s only through non-standard coding and setting method properties that it works, and even then you have very little control since the Interface being used for Enumeration, IEnumVariant is not easily implemented in VB6, and requires literally hacking away at your Process Memory to change in-memory VTables, which wreaks havoc on the brittle IDE.

Now, thanks to my Switch-over to C#, I can just declare those variables to be List<Block> or List<Level> or List<cBall> and forget about all this special Collection class nonsense. And Of course for specific Data Structures, I can use various other Generic classes such as Stack<>, LinkedList<>, etc. Generics is a powerful feature that becomes that much more valuable when you’ve had to hoof it yourself in less powerful languages to duplicate it’s functionality.

Dynamic Loading

But, this is supposed to be about dynamic loading- and more specifically, Reflection. Visual Basic 6 sorta-kinda had reflection, in the form of a MS-provided library, tlbinfo32.dll, which essentially allowed you to inspect COM components; since VB6 produced COM components, this worked there. But it was still rather cumbersome; you were no longer looking at VB-native classes or types, but you had to interpret the appropriate parameters from the idl code. additionally, COM was often just plain stubborn. I feel that in many ways .NET tries to resolve the issues of COM, particular with regards to pluggable components. .NET allows you to, directly in your code, load a new assembly, inspect the classes and types within that assembly- see what those classes implement or derive from, and so forth. Java provides equal functionality with it’s reflection libraries, too, and  that is how a lot of java applications implement plugins, including eclipse, one of the better java IDE applications.

With C# & .NET, you can load assemblies directly from the .dll or .exe file; then you have the Assembly object, and can inspect the types of t hat Object. BASeBlock, for example, loads all the assemblies (that don’t meet an “ignore” regular expression) from it’s executable’s folder- I’ll probably change this to a unique location, to prevent the game from enumerating the assemblies I included which will never contain what it is looking for). Once it has all the assemblies, it searches for various Types that implement some of it’s types. It always inspects “itself”, so it will find all the Block derivations, iBallBehaviour implementations, iPaddleBehaviour, etc. These get added to their own LoadedTypeManager instances within the MultiTypeManager that does all the work, and this allows the BASeBlock code to easily find all the implementations of it’s various interfaces, for purposes such as showing a pop-up menu and so forth.

Originally, I wanted to add a single implementation of the appropriate interfaces and base classes,  that delegated their actions/events/etc to some sort of script interpreter/compiler. After futzing about with IronPython, however, I decided  that the object model exposed there and what I needed required a lot of plumbing code to make managable. But then I had another thought.

The CodeDOM

.NET Exposes a namespace, System.Runtime.CompilerServices that contains more namespaces, classes, and interfaces that are used for dealing with the CodeDOM; it also allows you to compile  VB.NET and C# (probably F#, but since that isn’t included in .NET 3.5, I don’t support it yet), directly into assemblies. That is when I realized I could use that functionality, paired with my existing code that finds object implementations and derived classes for an Assembly[] array, to make it possible to “script” new derived classes and implementations of those interfaces and Derivation I search for. The result turned out to be quite simple, not more than a page of code, which I added to the class that performed the Assembly enumeration. It loads new assemblies by inspecting a Scripts folder in it’s %appdata% folder, and attempting to compile those scripts into assemblies (.csx files using the C# code provider, .vb files using the VB code provider… I was going to use .vbx but that gave me flashbacks to Visual Basic 2.0 and vbx extension files. The code used to compile adds the basic references (System.Drawing, System.data.dll, etc) as well as itself, using Assembly.GetExecutingAssembly().Location- this includes the BASeBlock assembly, which is necessary since BASeBlock of course contains all the interfaces that need to be implemented, the necessary frameworks for any additional addins, etc. The script I used to test was a copy-paste of the AddBallBlock class, whose name I changed to AddBallBlockEx. Once the code compiles the new assembly and detects everything went well, that Assembly object is added to the list of Assemblies that the game will inspect for the appropriate derivations- of Block, iBallBehaviour,GamePowerUp etcetera.

 

After some tweaks and debug sessions, everything seemed to work fine. So far the only issue has been that AddBallBlock’s code called “AddScore” which is implemented by the Block base class, but for some reason trying to compile that resulted in the error that the symbol wasn’t defined. Absolutely no idea what is going on there… Once I resolve that issue, I’ll make the error-detection code a bit more robust and easier to get to  (for people trying to write their own scripts, or me in the future, it would be nice to have a place to find the errors issued by the compiler; perhaps it can save them in either the same scripts folder (as script.csx.errors or something) or a separate folder. As it is now, though, the test classes I created seem to be found perfectly fine; the blocks show up in the editor and the powerups can be spawned using cheats, so they seem to work well. the AddScore() issue will bother me though, but it seems to be related to the way the object heirarchy is laid out.

The main “unique” feature of BASeBlock is that it uses pure GDI+; this was because OpenGL seemed like overkill, and I needed to learn GDI+ at the time anyway. People don’t give it enough credit, honestly.

Posted By: BC_Programming
Last Edit: 13 Nov 2011 @ 01:46 PM

EmailPermalinkComments (0)
Tags
Categories: Programming

 Last 50 Posts
 Back
Change Theme...
  • Users » 734
  • Posts/Pages » 98
  • Comments » 38
Change Theme...
  • VoidVoid « Default
  • LifeLife
  • EarthEarth
  • WindWind
  • WaterWater
  • FireFire
  • LightLight

PP



    No Child Pages.

Windows optimization tips



    No Child Pages.