using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using System.Xml;
namespace Slimzhao
{
/// <summary>
/// Summary description for EasyHelpString.
/// </summary>
public class EasyHelpString
{
/// <summary>
/// HelpProvider, control vs helpstring
/// </summary>
internal static HelpProvider global_hlp_provider = new HelpProvider();
static EasyHelpString()
{
try
{
string full_xml_name = Path.Combine(
Path.GetDirectoryName(Assembly.GetCallingAssembly().Location),
help_file);
FromXml(full_xml_name, assem_lower_name_VS_ArrayList_of_FormControl);
}
catch
{
}
}
/// <summary>
/// This uses ControlPaint.DrawReversibleFrame to highlight the given window
/// </summary>
internal static void FlashCurrentWindow(Control c)
{
Point topleft = c.Location;
if (c.Parent != null)
{
topleft = c.Parent.PointToScreen(topleft);
}
Size size = c.Size;
Rectangle r = new Rectangle(topleft, size);
for (int i = 1; i <= 7; i++)
{
ControlPaint.DrawReversibleFrame(r, Color.Red, FrameStyle.Thick);
Thread.Sleep(100);
}
Thread.Sleep(250); //extra delay at the end.
ControlPaint.DrawReversibleFrame(r, Color.Red, FrameStyle.Thick);
}
/// <summary>
/// Composer is negaviate the system.
/// </summary>
private static bool b_composing = true;
internal static string help_file = "help.xml";
/// <summary>
/// Init a form more than 1 times is safe
/// </summary>
/// <param name="container">Actually an instance of Form</param>
/// <param name="force_include">Controls force to include for adding context help, Note Label is default excluded</param>
public static void InitHelpProvider(Control container, params Control[] force_include)
{
InitHelpProvider(container, force_include, null);
}
/// <summary>
///
/// </summary>
/// <param name="container">Actually an instance of Form</param>
/// <param name="force_include">Controls force to include for adding context help, Note Label is default excluded</param>
/// <param name="force_exclude">Controls force to exclude for adding context help, the pervasive OK and cancel button maybe
/// the possible candicate</param>
public static void InitHelpProvider(Control container, Control[] force_include, Control[] force_exclude)
{
if( b_ever_call_AddNonParentUserControl == false)
{
throw new Exception(
"You should call AddNonParentUserControl at least once before the first call of InitHelpProvider");
}
if( container == null )
throw new ArgumentNullException("container");
StackTrace st = null;
if(false && b_composing )
{
st = new StackTrace();
if(st.GetFrame(1).GetMethod().Name != "InitHelpProvider")
{
set_help_setting(container);
}
}
foreach(Control c in container.Controls)
{
//bugfix: TabControl is not a ContainerControl, but can has Controls/Childs
Type t = c.GetType();
if( t == typeof(Label) &&
(force_include == null || Array.IndexOf(force_include, c /* bugfix: c <-> t */) < 0) )
continue;
if( force_exclude != null && Array.IndexOf(force_exclude, c) > -1 )
continue;
if( c.Controls.Count > 0 &&
s_non_parent_user_controls.Contains(c.GetType() ) == false )
{
InitHelpProvider(c, force_include, force_exclude);
}
else
{
set_help_setting( c );
}
}
if( st != null && s_editor != null &&
s_editor.Visible )
{
if(st.GetFrame(1).GetMethod().Name != "InitHelpProvider")
{
if(s_editor.Owner != null && s_editor.Owner.IsHandleCreated)
{
s_editor.Enabled = true;
}
s_editor.Hide();
}
}
}
/// <summary>
///
/// </summary>
/// <param name="container">Actually an instance of Form</param>
public static void InitHelpProvider(Control container)
{
Control[] includes = null;
Control[] excludes = null;
InitHelpProvider(container, includes, excludes);
}
/// <summary>
/// NumericUpDown is composed by an TextBox and a pair of button, such a control should be
/// considered as a final input facility, not a container.
/// </summary>
private static ArrayList s_non_parent_user_controls = new ArrayList();
private static bool b_ever_call_AddNonParentUserControl = false;
/// <summary>
/// Add or remove the types which control should be considered as the final user control, such as
/// the NumericUpDown, although it's composed of a TextBox and a pair of buttons
/// </summary>
/// <param name="add">Add or remove, Add control which is already added by other class is harmless</param>
/// <param name="nonParentUserControl">can be null</param>
public static void AddNonParentUserControl(bool add, Type[] nonParentUserControl)
{
b_ever_call_AddNonParentUserControl = true;
if( nonParentUserControl == null)
{
return;
}
foreach(Type t in nonParentUserControl)
{
if( add )
{
if( s_non_parent_user_controls.Contains(t) == false )
{//We need not to add the same element more than once
s_non_parent_user_controls.Add( t );
}
}
else
{
s_non_parent_user_controls.Remove( t );
Debug.Assert( s_non_parent_user_controls.Contains(t) == false,
string.Format("There's still a type of {0} after remove it", t.ToString() ));
}
}
}
/// <summary>
/// Set help string(for current ui culture) to HelpProvider, from memory or control's name
/// </summary>
/// <param name="c"></param>
private static void set_help_setting(Control c )
{
string help_str = "";
string cached_help_str = get_cached_help_str(c, null);
if( cached_help_str != null)
{
help_str = cached_help_str;
}
//Only set control with a name
//For the non name control, programmer can use a separate Help Provider
if( c.Name.Length > 0)
{
global_hlp_provider.SetShowHelp(c, true);
Debug.Assert(c.Name.Length > 0, "Control.Name.Length < 1");
global_hlp_provider.SetHelpString(c, help_str);
//TODO: Check only add it once
if( Control_VS_null.ContainsKey(c) == false)
{
c.HelpRequested += new HelpEventHandler(Global_HelpRequested);
Control_VS_null[c] = null;
}
}
}
/// <summary>
/// Key: Control instance which set_help_setting already add HelpEventHandler to it's HelpRequested
/// Value: null, not use
/// </summary>
private static Hashtable Control_VS_null = new Hashtable();
/// <summary>
/// Get a help string for current UI Culture from in memory cached data( maybe from XML file or
/// set by user)
/// </summary>
/// <param name="c"></param>
/// <param name="lower_lang_str">zh-chs, if null, use current UI Culture's</param>
/// <returns></returns>
internal static string get_cached_help_str( Control c, string lower_lang_str )
{
Form form = c.FindForm();
if(form == null)
{
Debug.Assert(false, "FindForm() = null");
throw new NullReferenceException("FindForm() = null");
}
Type t = form.GetType();
string lower_assem_name = Path.GetFileName(t.Assembly.Location).ToLower();
string class_name = t.FullName;
//Check Assembly
if( assem_lower_name_VS_ArrayList_of_FormControl.ContainsKey(lower_assem_name) == false)
return null;
//Check Form's Full Type Name
foreach(FormControl fc in
(assem_lower_name_VS_ArrayList_of_FormControl[lower_assem_name] as ArrayList) )
{
if( fc.FullFormName == class_name)
{
//get the lower lang str
string lc_lang_str = lower_lang_str;
if( lc_lang_str == null)
{
lc_lang_str = get_current_ui_lang_str();
}
Debug.Assert(lc_lang_str == "en" || lc_lang_str == "zh-chs" || lc_lang_str == "zh-cht",
string.Format("Unexpected lang str: {0}", lc_lang_str));
return fc.GetHelpStr(c.Name, //bugfix: change class_name to c.Name
lc_lang_str);
}
}
return null;
}
/// <summary>
/// get zh-chs
/// </summary>
/// <returns></returns>
internal static string get_current_ui_lang_str()
{
CultureInfo info = Thread.CurrentThread.CurrentUICulture;
string lc_lang_str = info.TwoLetterISOLanguageName + "-" + info.ThreeLetterWindowsLanguageName;
lc_lang_str = lc_lang_str.ToLower();
if(lc_lang_str.StartsWith("en-"))
{
lc_lang_str = "en";
}
return lc_lang_str;
}
private static EditHelpString s_editor = null;
/// <summary>
/// The HelpRequested event handler for all the Controls in the application
/// </summary>
/// <param name="sender"></param>
/// <param name="hlpevent"></param>
private static void Global_HelpRequested(object sender, HelpEventArgs hlpevent)
{
if( s_editor == null)
{
s_editor = new EditHelpString();
s_editor.CommitHelpString = new CommitHelpString_T( CommitHelpString );
}
Control c = sender as Control;
Form owner_form = c.FindForm();
Type form_t = owner_form.GetType();
string assem_name = Path.GetFileName(form_t.Assembly.Location);
//Popup the Help authoring window only when SHIFT is pressed
if ( (Native.GetAsyncKeyState( Native.VK_SHIFT) & 0x8000) != 0 )
{
s_editor.SetHelp(owner_form,
assem_name, form_t.FullName, c );
}
}
/// <summary>
/// Key: Lower case assembly name, TODO: maybe not sufficient
/// Value: ArrayList of FormControl
/// </summary>
internal static Hashtable assem_lower_name_VS_ArrayList_of_FormControl = new Hashtable();
/// <summary>
/// Send the help string to cache
/// </summary>
/// <param name="assembly_name"></param>
/// <param name="full_class_name"></param>
/// <param name="control"></param>
/// <param name="lang_str"></param>
/// <param name="help_str">If empty, remove the entry</param>
private static void CommitHelpString(string assembly_name, string full_class_name, Control control,
string lang_str, string help_str)
{
string assem_lower_name = assembly_name.ToLower();
if( assem_lower_name_VS_ArrayList_of_FormControl.ContainsKey(assem_lower_name) == false )
{
assem_lower_name_VS_ArrayList_of_FormControl[assem_lower_name] = new ArrayList();
}
ArrayList form_control_arr = assem_lower_name_VS_ArrayList_of_FormControl[assem_lower_name] as ArrayList;
bool found_form = false;
FormControl target_fc = null;
foreach(FormControl fc in form_control_arr)
{
if( fc.FullFormName != full_class_name )
continue;
found_form = true;
target_fc = fc;
}
if( found_form == false)
{
target_fc = new FormControl( full_class_name);
//bugfix: forget the following line to add to list
form_control_arr.Add(target_fc);
}
target_fc.SetHelpStr(control.Name, lang_str, help_str);
//bugfix: update current help string Only when get culture match.
//bugfix: help_str => lang_str
if( lang_str == get_current_ui_lang_str() )
{
global_hlp_provider.SetHelpString(control, help_str);
}
}
/// <summary>
/// Save cached help string to XML file
/// </summary>
internal static void ToXml()
{
string full_xml_name = Path.Combine(
Path.GetDirectoryName(Assembly.GetCallingAssembly().Location),
help_file);
//TODO: Create useful Xml Comment node
XmlDocument xml_doc = new XmlDocument();
//Create Element /HelpStr
XmlElement root = xml_doc.CreateElement("HelpStr");
xml_doc.AppendChild(root);
foreach(DictionaryEntry entry in assem_lower_name_VS_ArrayList_of_FormControl)
{
//Create Element /HelpStr/Assembly
string lower_assem_name = (entry.Key as string);
XmlElement elem_assem = xml_doc.CreateElement("Assembly");
elem_assem.SetAttribute("LowerName", lower_assem_name);
root.AppendChild(elem_assem);
foreach(FormControl fc in (entry.Value as ArrayList) )
{
//Create Element /HelpStr/Assembly/FormControl
XmlElement elem_form_control = xml_doc.CreateElement( "FormControl");
elem_form_control.SetAttribute("FullName", fc.FullFormName);
elem_assem.AppendChild(elem_form_control);
foreach(DictionaryEntry control_name_VS_lang_help in fc.ControlName_VS_Lang_Help)
{
string control_name = control_name_VS_lang_help.Key as string;
Hashtable lang_help = control_name_VS_lang_help.Value as Hashtable;
//Create Element /HelpStr/Assembly/FormControl/Control
XmlElement elem_control = xml_doc.CreateElement("Control");
elem_control.SetAttribute("Name", control_name);
elem_form_control.AppendChild(elem_control);
foreach(DictionaryEntry lang_VS_help in lang_help)
{
string lang_str = lang_VS_help.Key as string;
string help_str = lang_VS_help.Value as string;
//Create Element /HelpStr/Assembly/FormControl/Control/HelpStr
XmlElement elem_lang = xml_doc.CreateElement("HelpStr");
elem_lang.SetAttribute("LowerLangStr", lang_str);
elem_control.AppendChild(elem_lang);
//Create Element /HelpStr/Assembly/FormControl/Control/HelpStr/value
XmlElement elem_value = xml_doc.CreateElement("value");
elem_value.InnerText = help_str;
elem_lang.AppendChild(elem_value);
}
}
}
}
save_xml(xml_doc, full_xml_name);
}
/// <summary>
/// Load cached help string from XML file
/// </summary>
/// <param name="full_xml_fname"></param>
/// <param name="p_assem_lower_name_VS_ArrayList_of_FormControl"></param>
internal static void FromXml(string full_xml_fname, Hashtable p_assem_lower_name_VS_ArrayList_of_FormControl)
{
Debug.Assert( p_assem_lower_name_VS_ArrayList_of_FormControl != null);
XmlDocument xml_doc = new XmlDocument();
xml_doc.Load(full_xml_fname);
//TODO: Check schema
foreach( XmlElement assem_elem in xml_doc.SelectNodes("/HelpStr/Assembly"))
{
ArrayList list = new ArrayList();
p_assem_lower_name_VS_ArrayList_of_FormControl[assem_elem.GetAttribute("LowerName")] = list;
foreach(XmlElement elem_fc in assem_elem.SelectNodes("FormControl") )
{
FormControl form_control = new FormControl(elem_fc.GetAttribute("FullName") );
list.Add(form_control );
foreach(XmlElement elem_control in elem_fc.SelectNodes("Control"))
{
string control_name = elem_control.GetAttribute("Name");
foreach(XmlElement elem_help in elem_control.SelectNodes("HelpStr") )
{
form_control.SetHelpStr(control_name , elem_help.GetAttribute("LowerLangStr"),
elem_help["value"].InnerText );
}
}
}
}
}
private static void save_xml(XmlDocument doc, string full_xml_fname)
{
string tmp_fname = null;
XmlValidatingReader reader = null;
Stream schema_stream = null;
XmlTextWriter writer = null;
try
{
FileAttributes old_fa = FileAttributes.Normal;
if( File.Exists(full_xml_fname) )
{
old_fa = File.GetAttributes( full_xml_fname );
File.SetAttributes(full_xml_fname, old_fa & ~FileAttributes.ReadOnly );
}
tmp_fname = Path.GetTempFileName();
writer =
new XmlTextWriter(tmp_fname, new System.Text.UTF8Encoding(false));//no BOM mark
writer.Formatting = Formatting.Indented;
writer.Indentation = 2; //����2���ַ�
doc.Save( writer );
writer.Close(); writer = null;
//Check the resulting file by Xml schema
//If check ok, copy the file
if( File.Exists(full_xml_fname) ) File.Delete(full_xml_fname);
File.Move(tmp_fname, full_xml_fname);
File.SetAttributes(full_xml_fname, old_fa); //Resture the file attributes
Console.WriteLine("HERE");
}
catch(Exception ex)
{
Console.WriteLine("HERE Exception" + ex.ToString() );
Debug.Assert(false, "Temp file: " + tmp_fname + Environment.NewLine + ex.ToString() ) ;
}
finally
{
if(writer != null) //Write TMP file
writer.Close();
if(reader != null) //Xml validate reader <- TMP file
reader.Close();
if( schema_stream != null) //Embed xsd file stream
schema_stream.Close();
if( File.Exists( tmp_fname) )
File.Delete(tmp_fname);
}
}
/// <summary>
/// Merge the help specified in parameter into the current.
/// Conflicted help string should use another_help's
/// </summary>
/// <param name="merge">if true, just merge, ignore conflict</param>
/// <param name="conflict"></param>
/// <param name="another_help">Help generated by your colleague, same underlying structure as
/// assem_lower_name_VS_ArrayList_of_FormControl</param>
internal static void merge_help_str(bool merge, out Hashtable conflict, Hashtable another_help)
{
conflict = null;
try
{
merge_help_str_helper(merge, out conflict, another_help);
}
catch (Exception ex)
{
Debug.Assert(false, "Exception in merge_help_str" + Environment.NewLine + ex.ToString() );
}
}
private static void merge_help_str_helper(bool merge, out Hashtable conflict, Hashtable another_help)
{
conflict = null;
if( another_help == null)
throw new ArgumentNullException("another_help");
if( merge == false)
{
conflict = new Hashtable();
}
foreach(DictionaryEntry entry in another_help)
{
string new_assem_name = entry.Key as string;
ArrayList new_FormControl_vec = entry.Value as ArrayList;
if( assem_lower_name_VS_ArrayList_of_FormControl.ContainsKey( new_assem_name ) == false)
{
if( merge )
assem_lower_name_VS_ArrayList_of_FormControl.Add(new_assem_name, new_FormControl_vec);
continue;
}
//Search for the matched FormControl
ArrayList my_FormControl_vec = assem_lower_name_VS_ArrayList_of_FormControl[new_assem_name] as ArrayList;
foreach(FormControl new_fc in new_FormControl_vec)
{
FormControl my_fc = new_fc.find_name_matched(my_FormControl_vec);
if( my_fc == null)
{ //I have no such a FormControl
if( merge ) my_FormControl_vec.Add(new_fc);
continue;
}
//walk through he help strings for all language
foreach(DictionaryEntry new_ctrl_name_VS_lang_help in new_fc.ControlName_VS_Lang_Help)
{
string new_ctrl_name = new_ctrl_name_VS_lang_help.Key as string;
Hashtable new_lang_VS_help = new_ctrl_name_VS_lang_help.Value as Hashtable;
if( my_fc.ControlName_VS_Lang_Help.ContainsKey(new_ctrl_name) == false)
{
if( merge ) my_fc.ControlName_VS_Lang_Help[new_ctrl_name] = new_lang_VS_help;
continue;
}
Hashtable my_lang_VS_help = my_fc.ControlName_VS_Lang_Help[new_ctrl_name] as Hashtable;
foreach(DictionaryEntry lang_2_help in new_lang_VS_help)
{
//always add or replace even null or empty
if( merge )
{
my_lang_VS_help[lang_2_help.Key] = lang_2_help.Value;
continue;
}
string ori_helpstr = my_lang_VS_help[lang_2_help.Key] as string;
string new_helpstr = lang_2_help.Value as string;
if(ori_helpstr == null || ori_helpstr.Length == 0 || ori_helpstr == new_helpstr)
{//if origin help str is null or empty, or same as new help str, not considered conflict
continue;
}
if( conflict.ContainsKey(new_assem_name) == false )
{
conflict[new_assem_name] = new ArrayList();
}
ArrayList conflict_FormControl_vec = conflict[new_assem_name] as ArrayList;
FormControl conflict_fc = new_fc.find_name_matched( conflict_FormControl_vec );
if( conflict_fc == null)
{
conflict_fc = new FormControl(new_fc.FullFormName);
conflict_FormControl_vec.Add(conflict_fc);
}
if( conflict_fc.ControlName_VS_Lang_Help.ContainsKey(new_ctrl_name) == false )
{
conflict_fc.ControlName_VS_Lang_Help[new_ctrl_name] = new Hashtable();
}
Hashtable lang_VS_help = conflict_fc.ControlName_VS_Lang_Help[new_ctrl_name] as Hashtable;
lang_VS_help[lang_2_help.Key] = lang_2_help.Value;
}
}
}
}
}
/// <summary>
/// This function will Set DropDownWidth of ComboBox to the width for displaying even the longest item
/// should be invoked after UI initilized, espically after all the comboBox Items inited.
/// </summary>
/// <param name="page">The topmost control, all it's child or grandchild or ... will be set</param>
public static void InitComboBoxDropDownWidth(Control page)
{
try
{
if( page is ComboBox)
{
InitSingleComboBoxDropDownWidth( (ComboBox)page );
return;
}
foreach(Control c in page.Controls)
{
if( c.Controls.Count > 0)
{
InitComboBoxDropDownWidth( c ); //recursive
}
if(c is ComboBox && ((ComboBox)c).Items.Count > 0)
{
InitSingleComboBoxDropDownWidth( (ComboBox)c );
}
}
}
catch(Exception e)
{
Debug.Assert(false, e.ToString() );
}
}
internal static void InitSingleComboBoxDropDownWidth(ComboBox box)
{
using(Graphics g = box.CreateGraphics() )
{
int width = box.Width;
for(int i = 0; i < box.Items.Count; i++)
{
SizeF size = g.MeasureString( box.GetItemText( box.Items[i] ), box.Font );
width = Math.Max(width, (int)Math.Floor( size.Width) );
}
box.DropDownWidth = width;
g.Dispose();
}
}
}
internal class FormControl
{
private string m_full_form_name;
private Hashtable m_control_name_VS_lang_help = new Hashtable();
/// <summary>
/// Key: Control name, case sensitive
/// Value: Hashtable (lang_help)
///
/// lang_help.Key: lower case lang str, e.g., zh-chs
/// lang_help.Value: multi-line help string
/// </summary>
internal Hashtable ControlName_VS_Lang_Help
{
get { return m_control_name_VS_lang_help; }
}
/// <summary>
///
/// </summary>
/// <param name="v_this">Item is FormControl</param>
/// <returns></returns>
internal FormControl find_name_matched(ArrayList v_this)
{
Debug.Assert( v_this != null );
foreach(FormControl c in v_this)
{
if( FullFormName == c.FullFormName)
return c;
}
return null;
}
internal string FullFormName
{
get { return m_full_form_name; }
}
internal FormControl(string fullFormName)
{
if( fullFormName == null)
{
Debug.Assert(false, "fullFormName = null");
throw new ArgumentNullException("fullFormName", "fullFormName = null");
}
m_full_form_name = fullFormName;
}
/// <summary>
///
/// </summary>
/// <param name="control_name"></param>
/// <param name="langString"></param>
/// <param name="helpString">If Empty, remove the entry</param>
internal void SetHelpStr(string control_name, string langString, string helpString)
{
DefensiveNullChecking(control_name, langString, helpString);
//The real things
if( m_control_name_VS_lang_help.ContainsKey(control_name) == false)
{
m_control_name_VS_lang_help[control_name] = new Hashtable();
}
Hashtable lang_help = m_control_name_VS_lang_help[control_name] as Hashtable;
if( helpString.Length > 0)
{
lang_help[langString] = helpString;
}
else
{
lang_help.Remove( langString );
}
}
internal string GetHelpStr(string control_name, string langString)
{
DefensiveNullChecking(control_name, langString);
if( m_control_name_VS_lang_help.ContainsKey(control_name) == false)
{
return null;
}
Hashtable lang_help = m_control_name_VS_lang_help[control_name] as Hashtable;
string lower_lang_str = langString.ToLower();
if( lang_help.ContainsKey( lower_lang_str ) == false )
{
return null;
}
Trace.Assert( (lang_help[lower_lang_str] == null) ||
(lang_help[lower_lang_str] is string),
string.Format("Expect type of lang_help is string, actual: {0}",
lang_help[lower_lang_str].ToString() ));
return lang_help[lower_lang_str] as string;
}
private void DefensiveNullChecking(params object[] all_parameters)
{
//TODO: how to get the real parameter name
foreach(object o in all_parameters )
{
if( o == null )
{
Debug.Assert(false, "parameter = null");
throw new ArgumentNullException("parameter", "parameter = null");
}
}
}
public override string ToString()
{
Debug.Assert( m_full_form_name != null, "m_full_form_name is null");
return m_full_form_name;
}
}
class Native
{
[DllImport("user32.dll")]
internal static extern int ReleaseCapture ();
[DllImport("user32.dll")]
internal static extern int SendMessage (
IntPtr hwnd,
int wMsg,
int wParam,
int lParam);
[DllImport("user32.dll")]
internal static extern int EnableWindow (
int hwnd,
int fEnable);
[DllImport("user32.dll")]
internal static extern int SetForegroundWindow (
int hwnd);
[DllImport("user32.dll", CallingConvention=CallingConvention.Winapi)]
internal extern static short GetAsyncKeyState(int vKey);
internal const int VK_SHIFT = 0x10;
internal const int WM_NCLBUTTONDOWN = 0xA1;
internal const int HTCAPTION = 0x2;
}
internal delegate void CommitHelpString_T(string assembly_name, string full_class_name, Control control,
string lang_str, string help_str);
}