HotDog's Blog

Hotdog (Robert Verpalen) about C# and vb.net

This blog hosted by:
http://blogs.vbcity.com      
  Home :: Syndication  :: Login

JanFebruary 2006Mar
SMTWTFS
2930311234
567891011
12131415161718
19202122232425
2627281234
567891011

Articles

Archives

Topics

CONTACT

Fun but useful linkies

General

VS 2005

Wolfenstein ET

Friday, February 10, 2006 #

Another old (and disappointing) project, was to quickly compress strings a bit, by not using the complete ascii range and certainly not the whole unicode range.
At first I used this class to save a forms typename as an identifier, using 6 bits per letter. Besides being a bit gratifying in completing the puzzle, the usefullness of this class is... well.... let's say... not so very great. Right now I'm using it for a quick serializer where the use of it on cummalative typenames can make some differences, but probably not much in comparisson to real compression algorithms such as zipping.

Why post it then? Good question. Mainly because I want to be able to find it back when doing such a 'puzzle' again. And who knows, maybe someday this can be further build in a better sorting algorithm, and partly because some of you out there might actually have a use for the bitwise storing used in this class (who knows eh? :D )

Code CopyHideScrollFull
namespace Subro.Text
{
using System;
using
System.Collections;
/// <summary>
///
Class to create a compacter version of a string (and back).
///
Not the same as a compression algorithm, but a quick way to
///
store a string minimized
///
</summary>
///
<example>
///
Subro.Text.Compactor c = new Subro.Text.CompactorAscii();
///
string t = "This is a test, does it compute?"
///
byte[] b;
///
b = c.Compact(t);
///
t = c.DeCompact(b);
///

///
Type ty = typeof(Form1);
///
c = new Subro.Text.CompactorTypeName();
///
b = c.Compact(ty.FullName);
///
ty = (c as Subro.Text.CompactorTypeName).GetType(b);
///
</example>
public
class Compactor
{
readonly string allow;
readonly
int bitgroup;
public
Compactor(string AllowedValues)
{
allow = AllowedValues;
bitgroup = (int)Math.Ceiling(Math.Log(allow.Length + 1, 2));
}
///
<summary>
///
Uses the default allowed values: <see cref="CompactStringBase"/>
///
<see cref="ThrowOnInvalidChars"/> is set to false, so other characters
///
are simply ignored
///
</summary>
public
Compactor()
: this(CompactStringBase)
{
ThrowOnInvalidChars = false;
}
public byte[] Compact(string value)
{
return Compact(value, GetLength(value));
}
public
byte[] Compact(string value, uint FixedLength)
{
byte[] b = new byte[FixedLength];
unchecked

{
int pos = 0, bitpos = 0;
foreach
(char ch in value)
{
int i = allow.IndexOf(ch) + 1;
if
(i > 0)
{                        
i <<= bitpos;
b[pos] += (byte)i;
if
((bitpos+=bitgroup) >= 8)
{
if (++pos == FixedLength) break;                            
bitpos -= 8;
if
((i >>= 8) == 0) continue;
b[pos] = (byte)i;
}
}
else
if (ThrowOnInvalidChars)
throw new Exception("Character is not allowed with the current settings: " + ch.ToString());
}
}
return
b;
}
/// <summary>
///
The amount of bits one character will take, using the allowed string
///
</summary>
public
int BitGroup
{
get { return bitgroup; }
}
/// <summary>
///
The mask that can be used with an AND against data to get (the most right)
///
letter index
///
</summary>
public
int Mask
{
get { return ((1 << bitgroup) - 1); }
}
/// <summary>
///
Returns the expected length of the byte array returned when compacting
///
a string with the currently allowed characters
///
This is NOT subtracting invalid characters when <see cref="ThrowOnInvalidChars"/>
///
is disabled
///
</summary>
///
<param name="Value"></param>
///
<returns></returns>
public
uint GetLength(string Value)
{
return (uint)Math.Ceiling(Value.Length * bitgroup / (double)8);
}
/// <summary>
///
Returns the expected length or the resulting string when Decompacting this binary data.
///
This is NOT with subtracting 0 values
///
(PS, the length is the length of characters. Since in .net all strings are unicode,
///
  the memory usage will be greater than that)
///
</summary>
///
<param name="b"></param>
///
<returns></returns>
public
int GetLength(Byte[] b)
{
return 8 * b.Length / bitgroup;
}
/// <summary>
///
determines if an exception should be thrown if <see cref="Compact"/> encounters
///
characters that are not in the Allowed string.
///
If this value is false, those chars are simply skipped
///
</summary>
public
bool ThrowOnInvalidChars = true;
public string DeCompact(byte[] bytes)
{
if (bytes==null || bytes.Length == 0) return null;
char
[] chars = new char[GetLength(bytes)];
BitArray ba = new BitArray(bytes);
int
charpos = 0;
int
max = chars.Length * bitgroup;
for (int i = 0; i < max;)
{
int index = 0;
for
(int j = 0; j < bitgroup; j++)
{
if(ba[i++])
index+= 1<<j;
}
if
(index == 0) break;
chars[charpos++] = allow[index - 1];
}
return
new string(chars, 0, charpos);
}
/// <summary>
///
Biggest Allowed values possible for keeping 5 bit compacting
///
</summary>
public
const string CompactStringBase = "4321ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public
const string CompactStringForm = "|%21ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public
const string CompactStringVariableName = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
///
<summary>
///
String with allowed characters for a .net type name (results in 6 bit storage)
///
</summary>
public
const string CompactStringTypeName = CompactStringVariableName + ".";
///
<summary>
///
A string with all the normal ascii characters (results in 8 bit storage, so a normal byte and therefor not very useful)
///
</summary>
public
static string CompactStringNormalAscii
{
get
{
string s = "";
for
(int i = 1; i < 256; i++)
s += (char)i;
return s;
}
}
}
public class CompactorTypeName : Compactor
{
public CompactorTypeName()
: base(CompactStringTypeName)
{
}
public byte[] Compact(Type t)
{
return Compact(t.FullName);
}
public Type GetType(byte[] b)
{
return Type.GetType(DeCompact(b));
}
}
/// <summary>
///
A compacter using the <see cref="CompactStringNormalAscii"/>
///
Not very usefull, you might as well use the Ascii encoding.
///
The length of bytes will simply be the same as the string length.
///
But included the possibility if a compactor range has to be increased quickly
///
without the need for rewriting to much ;-)
///
</summary>
public
class CompactorAscii : Compactor
{
public CompactorAscii()
: base(CompactStringNormalAscii)
{
}
}

}
. . .

Example usage:
Code CopyHideScrollFull
Subro.Text.Compactor c = new Subro.Text.CompactorAscii();
string
t = "This is a test, does it compute?";
byte
[] b;
b = c.Compact(t,50);
t = c.DeCompact(b);
Type ty = typeof(Form1);
c = new Subro.Text.CompactorTypeName();
b = c.Compact(ty.FullName);
ty = (c as Subro.Text.CompactorTypeName).GetType(b);
//The above didn't really make a difference
//The class is more intended for known results

//Eg, results of ABCD, without having to worry about

//indexing to numbers yourself (bad example, because

//generally, that would mean a bad design, but had to have some

//sort of example :-/ )

c = new Subro.Text.Compactor("ABCDE");
b = c.Compact("AAAEABBCCCCDDABCAADAAEBCAADDAACCDDAADDCCAAADDBBBDDACDEAAEEE");
t = c.DeCompact(b);
. . .
posted @ 9:22 AM

Was looking for some old code in an unfinished project and stumbled upon some code that I meant to publish, but don´t believe I did :-/
It's a simple component to quickly save properties of a form and/or controls on it. A bit obsolete now because the application settings in .net 2.0 are build in and can be used as is, and with a larger scope. The truth is: I don't even use this component, but use the application settings instead :rolleyes:

Ah well, for those that are interested in such a component all the same, here it is.

Code CopyHideScrollFull


#define
UseDesigner //comment this line if no designer should be used.
//the designer functionality encompasses the ability to choose from a list of
//properties when adding a control to the quicksaver.
//without the define, the property must be typed in manually.
//The only extra thing that is required when using the designer is a reference
//to System.Design.dll. If you don't want to add that reference, comment or remove
//this define

using
System;
using
System.Text;
using
System.ComponentModel;
using
System.Windows.Forms;
using
System.Collections.Generic;
using
System.Reflection;
using
System.Data;
using
System.IO;

namespace
Subro.Components
{
[ProvideProperty("SaveProperty", typeof(Control))]
[ProvideProperty("IncludeInSave", typeof(Control))]
[ProvideProperty("SaveDataSource", typeof(ListControl))]
public
class QuickSaver : Component, IExtenderProvider,ISupportInitialize
{
public QuickSaver()
{
}
public
QuickSaver(IContainer Container)
{
Container.Add(this);
}
#region IExtenderProvider Members
public bool CanExtend(object extendee)
{
return extendee is Control;
}
#endregion
const string MyCat = "Saving";
Dictionary<Control, PropertyInfo> props = new Dictionary<Control, PropertyInfo>();
[DefaultValue(null)]
[Category(MyCat)]
#if UseDesigner
[Editor(
typeof(QuickSaver.QuickSaverEditor),
typeof
(System.Drawing.Design.UITypeEditor))]
#endif
public string GetSaveProperty(Control c)
{
if (c == null) return null;
PropertyInfo
info;
if
(props.TryGetValue(c, out info))
return info.Name;
return null;
}
[DefaultValue(false)]
[Category(MyCat)]
[Description("Not implemented yet")]
public
bool GetSaveDataSource(ListControl c)
{
return false;
}
public
void SetSaveDataSource(ListControl c,bool value)
{
}
Form form;
public
void SetSaveProperty(Control c, string Property)
{
if (c == null) return;
needcheck = true;
if
(Property == null)
{
props.Remove(c);
if
(props.Count == 0)
form = null;
}
else

{
PropertyInfo info = c.GetType().GetProperty(Property);
if
(info == null) throw new Exception("Could not find property " + Property + " on " + c.GetType().Name);
props[c] = info;                
}
CheckValues();
}
void setAutoLoadSave()
{
if (initializing || form ==null) return;
EventHandler
ehload = new EventHandler(form_Load);
FormClosedEventHandler
ehclose = new FormClosedEventHandler(form_FormClosed);
form.Load -= ehload;            
form.FormClosed -= ehclose;
if (useautoloadsave)
{
form.Load += ehload;
form.FormClosed += ehclose;
}
}
void form_FormClosed(object sender, FormClosedEventArgs e)
{
Save();
}
void form_Load(object sender, EventArgs e)
{
Load();
}
private bool useautoloadsave = true;
[DefaultValue(true)]
[Category(MyCat)]
[Description("If this value is true, settings are automatically loaded when the form on which the controls are places is loaded and saved when that form is closed")]
public
bool UseAutoLoadSave
{
get { return useautoloadsave; }
set

{
if (value == useautoloadsave) return;
useautoloadsave = value;
setAutoLoadSave();
}
}

[DefaultValue(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Category(MyCat)]
public
bool GetIncludeInSave(Control c)
{
return props.ContainsKey(c);
}
public void SetIncludeInSave(Control c, bool value)
{
if (value == GetIncludeInSave(c)) return;            
//try to get some defaults for saving (edit as you please ;-) )    
string
p;
if
(!value)
p = null;
else if (c is NumericUpDown || c is DateTimePicker)
p = "Value";
else if (c is CheckBox)
p = "CheckState";
else //for the rest, use the Text property
p = "Text";

SetSaveProperty(c, p);    
}
bool
needcheck;

private SavePath spath;
[Category(MyCat)]
[DefaultValue(false)]
[Description("Determines if the settings saved, are for the current user only, for the current computer in the application settings or in the executable folder. Only does something if FileName is empty or doesn't contain a full path. NB: User and App settings are saved using the assembly version number, so take care in use! If the version is set to upgrade itself each time with a new build (VS default setting), the file is recreated each build")]
public
SavePath SaveMode
{
get { return spath; }
set
{spath = value;}
}

private string filename;
[Category(MyCat)]
[Description("The filename where the data is saved to/loaded from. If this value is empty (default), a default file will be generated depending on the setting of ForCurrentUser. If no root is given the path will also be completed in the same manner")]
[DefaultValue(null)]
public
string FileName
{
get { return filename; }
set

{filename = value;}
}
/// <summary>
///
The complete filename to which actually will be saved to/loaded from.
///
NB, this function returns null when there are no entries
///
</summary>
[Browsable(false)]
public
string GetSaveFile(bool LookForPrevious)
{
if (!HasEntries)
return null;
CheckValues();
string savefile = filename == null
? form.Name + ".dat"
: filename.Trim();
return Constants.GetSaveFile(spath, savefile, LookForPrevious);
}
/// <summary>
///
<c>true</c> is Controls are attached to this component for saving, otherwhise <c>false</c>
///
</summary>
[Browsable(false)]
public
bool HasEntries
{
get { return props.Count > 0; }
}
/// <summary>
///
uhm.. this uhm, does the loading
///
</summary>
public
void Load()
{
if (!HasEntries) return;
CheckValues();
string
file = GetSaveFile(true);
if
(!File.Exists(file)) return;
DataSet
ds = new DataSet();
ds.ReadXml(file);
DataTable
dt = ds.Tables[0];
DataRow
dr = dt.Rows[0];//add check?
foreach
(KeyValuePair<Control,PropertyInfo> e in props)  
{
int i = dt.Columns.IndexOf(e.Key.Name);
if
(i == -1) continue;
object
value = Convert.ChangeType(dr[i], e.Value.PropertyType);
e.Value.SetValue(e.Key, value, null);
}
//TODO: add datasources
}
public void Save()
{
if (!HasEntries) return;
CheckValues();
DataSet
ds = new DataSet();
DataTable
dt = new DataTable();
//create table

object
[] values = new object[props.Count];
int
i = 0;
foreach
(KeyValuePair<Control, PropertyInfo> e in props)
{
dt.Columns.Add(e.Key.Name, e.Value.PropertyType);
values[i++] = e.Value.GetValue(e.Key,null);
}
dt.Rows.Add(values);
ds.Tables.Add(dt);
ds.WriteXml(GetSaveFile(false));
//TODO: add datasources
}
void CheckValues()
{
if (!needcheck || initializing) return;

foreach
(Control c in props.Keys)
{
Form f = c.FindForm();
if
(f == null)
throw new Exception("Quicksaver expects controls to be added to a form to be able to load or save its values\n" + c.Name + " does not belong to a form");
if (form == null)
form = f;
else if(f != form)
throw new Exception("Quicksaver expects all controls to be part of the same form. At least one control was part of another form");
}
setAutoLoadSave();
needcheck = false;
}
#region ISupportInitialize Members
bool initializing;
public
void BeginInit()
{
initializing = true;
}
public void EndInit()
{
initializing = false;
if
(!DesignMode)
CheckValues();
}
#endregion
#if UseDesigner
#region
Designer

public class QuickSaverEditor : System.Drawing.Design.UITypeEditor
{
public override System.Drawing.Design.UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return System.Drawing.Design.UITypeEditorEditStyle.DropDown;
}
System.Windows.Forms.Design.IWindowsFormsEditorService
edSvc;
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, System.IServiceProvider provider, object value)
{
Control c = context.Instance as Control;
if
(c == null) return null;
// Uses the IWindowsFormsEditorService to display a
// drop-down UI in the Properties window.

edSvc =
(System.Windows.Forms.Design.IWindowsFormsEditorService)provider.GetService(typeof(System.Windows.Forms.Design.IWindowsFormsEditorService));
if (edSvc != null)
{
ListBox lb = new ListBox();
lb.BeginUpdate();
lb.Items.Add("<none>");
foreach
(PropertyInfo info in c.GetType().GetProperties())
lb.Items.Add(info.Name);
lb.SelectedItem = value;
lb.SelectedIndexChanged += new EventHandler(lb_SelectedIndexChanged);
edSvc.DropDownControl(lb);
if (lb.SelectedIndex < 1) return null;
return
lb.SelectedItem.ToString();
}
return
value;
}
void lb_SelectedIndexChanged(object sender, EventArgs e)
{
edSvc.CloseDropDown();
}
}
#endregion
#endif
}
}

namespace
Subro
{
using System.IO;
public enum SavePath
{
UserSettings, ApplicationSettings, ExecutableFolder
}
public
static partial class Constants
{
public static string GetSaveFile(SavePath spath,string FileName)
{
return GetSaveFile(spath, FileName, false);
}
public
static string GetSaveFile(SavePath spath,string FileName,bool LookForPrevious)
{
if (
System.Text.RegularExpressions.Regex.IsMatch(
FileName, @"(?:[a-z]:\\|\\\\)[a-z]"))
{
return FileName;
}
string folder;
switch
(spath)
{
case SavePath.UserSettings:
folder = Application.UserAppDataPath;
break
;
case SavePath.ApplicationSettings:
folder = Application.CommonAppDataPath;
break
;
case SavePath.ExecutableFolder:
folder = new FileInfo(Application.ExecutablePath).DirectoryName;
LookForPrevious = false;
break
;
default:
return null;                    
}
if
(FileName[0] != '\\') FileName = "\\" + FileName;
FileName = folder + FileName;
if (LookForPrevious)
{
SearchPrevious(ref folder, ref FileName);
}
return
FileName;
}
/// <summary>
///
In case of Application.UserAppDataPath and Application.CommonAppDataPath a
///
different directory is created for each assembly version (see the AssemblyInfo file
///
added to your project). This can of course be very inconvenient for loading data from
///
an older version. This method searches for previous versions.
///
</summary>
///
<param name="folder"></param>
///
<param name="FileName"></param>
static
void SearchPrevious(ref string folder,ref string FileName)
{
FileInfo fi = new FileInfo(FileName);
if (!fi.Exists)
{
string f = "\\" + fi.Name;
DirectoryInfo
[] dis =new DirectoryInfo(folder).Parent.GetDirectories();
string
[] files = new string[dis.Length];
int
i=0;
foreach
(DirectoryInfo di in dis)
{
files[i++] = di.FullName + f;
}
Array
.Sort<string>(files);
for (i = files.Length - 1; i >= 0; i--)
if (File.Exists(files[i]))
{
FileName = files[i];
break
;
}
}
}
}
}
. . .
posted @ 12:59 AM