2011年6月14日 星期二

(C#)利用圖示合併出企業流程所需圖示(三)

不知道有沒有研究過Windows系統自vista以後,執行檔有些會出現一個盾牌圖示,可是如果用圖示工具瀏覽該執行檔,會發現存在於此執行檔內的圖示並沒有加上盾牌。

如C:\Windows\System32\wusa.exe

2011-06-14_135435

微軟不可能對未知或數以萬計的執行檔去做不同的圖示,而是當檔案屬性不同時explorer會針對這些檔案的圖示加上一些標示,要合併兩個圖示其實應該沒有很困難,可是真的要做時又沒有想像中的輕而易舉。

首先,由下面的文章獲取了合併圖示的方式

http://stackoverflow.com/questions/2599778/how-to-merge-two-icons-together-overlay-one-icon-on-top-of-another

public Bitmap getOverlayIcon(Icon originalIcon, Icon overlay)
{
//由系統圖示中取得超大圖示
using (Image a = ExtractExtraSizeIcon(originalIcon))
using (Image b = overlay.ToBitmap())
using (var bitmap = new Bitmap(256, 256))
using (var canvas = Graphics.FromImage(bitmap))
{
Rectangle r = new Rectangle(new Point(0, 0), new Size(256, 256));
ColorMatrix cmxPic = new ColorMatrix();
cmxPic.Matrix33 = 1.0f;

ImageAttributes iaPic = new ImageAttributes();
iaPic.SetColorMatrix(cmxPic, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

ColorMatrix cmxPic2 = new ColorMatrix();
//cmxPic2.Matrix33 = 0.5f; 半透明
cmxPic2.Matrix33 = 1.0f;

ImageAttributes iaPic2 = new ImageAttributes();
iaPic2.SetColorMatrix(cmxPic2, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);


canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.DrawImage(a, r, 0, 0, 256, 256, GraphicsUnit.Pixel, iaPic);
canvas.DrawImage(b, r, -256, -256, 384, 384, GraphicsUnit.Pixel, iaPic2);
canvas.Save();

bitmap.Save("output.ico", ImageFormat.Icon);
pictureBox3.Image = new Bitmap("output.ico");
return bitmap;
}
}




在繪製第一張圖實沒有問題,大小設定在256 x 256 超大圖示,但在繪製第二張圖時如果要達到標示的效果,就需要調整一下位置,我選用了一張128 x 128 的圖示,經過上面程式碼的合併後可得到下面的圖示:


2011-06-14_222353


利用上面的動作搭配上COM組件的佈署,就可以讓explorer在檔案呈現時展現多樣化,在視覺效過的開發上有很大的幫助。


測試檔下載

2011年6月13日 星期一

(C#)匯出系統檔案類型中的超大圖示(二)

因為專案開發的需要,需要轉存檔案圖示經過處理後產生專案所需要的圖示,但是使用SHGetFileInfo()或ExtractIconEx()等方法轉存出來的圖示最大也只有32 x 32,感覺不是很滿意,因為微軟系統自Vista以後就支援超大圖示,為什麼無法得到超大圖示,於是上網搜尋研究了一下,發現很多人遇到一樣的問題,下面這篇討論似乎可以得到一些線索:

http://stackoverflow.com/questions/220465/using-256-x-256-vista-icon-in-application

但是我用了上面的程式碼匯出圖示發現依然不是超大圖示,後來由下面這篇文章中的工具發現shell32.dll中的圖示最大也只有48 x 48,如果能會出256 x 256的超大圖示那就見鬼了。

http://www.codeproject.com/KB/cs/IconLib.aspx

所以繼續發揮google搜尋的強大功能,由下面這篇文章又知道了原來在登錄資料庫(Registry)中對每一種檔案類型都有預設的圖示(DefaultIcon)。

http://www.codeproject.com/KB/cs/IconExtractor.aspx

由上面的專業文章就知道,如果我要匯出特定檔案類型的超大圖示,必須先由登錄資料庫中查詢到檔案類型預設圖示所參照的DLL和其在DLL中的位置(index)。

string regIconString = null;
string systemDirectory = Environment.SystemDirectory + "\\";
string regFileType = null;
if (fileType[0] == '.')
{
try
{
regFileType = Registry.ClassesRoot.OpenSubKey(fileType, false).GetValue("
").ToString();
if (regFileType != null)
{
regIconString = Registry.ClassesRoot.OpenSubKey(regFileType +
@"
\DefaultIcon", false).GetValue("").ToString();
}
}
catch
{
regIconString = null;
}

if (regIconString == null)
{
regIconString = systemDirectory + "
imageres.dll,0";
}
}
else
{
regIconString = systemDirectory + "
imageres.dll,3";
}
label1.Text = regIconString;
string[] fileIcon = regIconString.Split(new char[] { ',' });
if (fileIcon.Length != 2)
{
fileIcon = new string[] { systemDirectory + "
imageres.dll", "2" };
}



找到正確的DLL,就可以透過IconExtractor將圖示取出,此時取出的圖示其實是一個Icon的陣列,裏面包含不同size和解析度的圖示,這時再利用下面的方法就可以轉存超大圖示

public void ExtractExtraSizeIcon(Icon icoIcon)
{
Bitmap bmpPngExtracted = null;
try
{
byte[] srcBuf = null;
using (MemoryStream stream = new MemoryStream())
{
icoIcon.Save(stream);
srcBuf = stream.ToArray();
}
const int SizeICONDIR = 6;
const int SizeICONDIRENTRY = 16;
int iCount = BitConverter.ToInt16(srcBuf, 4);
for (int iIndex = 0; iIndex < iCount; iIndex++)
{
int iWidth = srcBuf[SizeICONDIR + SizeICONDIRENTRY * iIndex];
int iHeight = srcBuf[SizeICONDIR + SizeICONDIRENTRY * iIndex + 1];
int iBitCount = BitConverter.ToInt16(srcBuf, SizeICONDIR + SizeICONDIRENTRY * iIndex + 6);
if (iWidth == 0 && iHeight == 0 && iBitCount == 32)
{
int iImageSize = BitConverter.ToInt32(srcBuf, SizeICONDIR + SizeICONDIRENTRY * iIndex + 8);
int iImageOffset = BitConverter.ToInt32(srcBuf, SizeICONDIR + SizeICONDIRENTRY * iIndex + 12);
MemoryStream destStream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(destStream);
writer.Write(srcBuf, iImageOffset, iImageSize);
destStream.Seek(0, SeekOrigin.Begin);
bmpPngExtracted = new Bitmap(destStream);
break;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
string fileName = saveFileDialog1.FileName;

bmpPngExtracted.Save(fileName,ImageFormat.Icon);
}

下面是我測試的專案,可以得到256 x 256的超到圖示


2011-06-14_110945


範例下載

2011年6月12日 星期日

(C#)利用Microsoft COM組件變更檔按圖示(一)

Windows平台下檔案圖示的呈現、變更與清理,基本上應該是透過explorer.exe程序處理的。在系統安裝完成後系統對於一般檔案有其預設的檔案型態定義與圖式,雖然這些圖示可以透過管理介面變更,但其實都是在變更系統登錄資料庫中對檔案類型的icon值作定義,一旦變更後會改變系統中所有對此類型檔案的圖示加以變更,並無法針對單一檔案做處理。

至於新安裝的應用軟體和其所屬的檔案型態,在完成安裝時必須告訴explorer其對應的檔案副檔名與檔案圖示。

針對單一檔案圖示的變更,微軟也提供許多應用程式開發時的工具,透過不同的方式來變更單一檔案的圖示而不改變相同檔案類型的其他圖示,其中較為普遍的就是利用COM(Component)組件來完成這項工作。

由於系統的多功,複雜了系統的處理邏輯,微軟為了應付大量的系統功能變更與更新,將系統許多功能模組拆解成功能性的組件,這些組件都以shell interface的方式定義,裡面包含了相關的屬性與方法,利用物件繼承的特性實作這些方法與改變物件屬性便可以改變系統的判斷行為。Explorer.exe只需要針對用戶發出的event透過QueryInterface()查詢到相關的interface來處理用戶端的動作即可。

下面是我實作這些interface的展示:

http://msdn.microsoft.com/en-us/library/bb774328(v=VS.85).aspx

微軟定義了非常多的shell interface來給開發者使用,其中IExtractIcon定義了如何由物件中取出icon及其位置的方法,然後加以處理置換。

我利用這個interface來實作我所想要的動作,範例以c#來作處理,當然也可以用c++,不過目的都一樣。

(1) 編輯一個c#類別來定義要實作的shell interface

下面為IExtractIcon的原型,必須先定義。

[ComVisible(true), ComImport, Guid("000214eb-0000-0000-c000-000000000046"), 
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IExtractIcon
{
[PreserveSig]
int GetIconLocation([In] ExtractIconOptions uFlags,
[In] IntPtr szIconFile,
[In] uint cchMax,
[Out] out int piIndex,
[Out] out ExtractIconFlags pwFlags);
[PreserveSig]
int Extract([In, MarshalAs(UnmanagedType.LPWStr)] string pszFile,
uint nIconIndex,
[Out, MarshalAs(UnmanagedType.CustomMarshaler,
               MarshalTypeRef=typeof(IconMarshaler))] out Icon phiconLarge, 
[Out, MarshalAs(UnmanagedType.CustomMarshaler,
               MarshalTypeRef=typeof(IconMarshaler))] out Icon phiconSmall, 
[In] uint nIconSize);
}






此外變更圖示的動作要看是在甚麼樣的動作驅動下變更的,由msdn中可以查到一個叫IPersistFile的COM interface



http://msdn.microsoft.com/zh-tw/library/system.runtime.interopservices.comtypes.ipersistfile(v=vs.85).aspx



由這個interface可以讓應用程式知道用戶正在對檔案做甚麼樣的動作,下面為interface的原型,開發者必須在應用程式中實作下面方法。

    [ComImport(), ComVisible(true), InterfaceType(ComInterfaceType.


InterfaceIsIUnknown), GuidAttribute("0000010b-0000-0000-C000-000000000046")]
public interface IPersistFile
{
[PreserveSig]
uint GetClassID(out Guid pClassID);
[PreserveSig]
uint IsDirty();
[PreserveSig]
uint Load([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName,
                  [In] uint dwMode);
[PreserveSig]
uint Save([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName,
                  [In] bool fRemember);
[PreserveSig]
uint SaveCompleted([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName);

[PreserveSig]
uint GetCurFile([MarshalAs(UnmanagedType.LPWStr)] out string ppszFileName);
}
 






(2) 實作interface



實作的部分最主要是要處理當shell讀取或寫入檔案時如何置換檔案圖示。由範例中程式主要是判斷檔案內容來決定要用何種圖示取代。



if (text.ToUpper().StartsWith("GWGDE"))
{
Icon phicon = getOverlayIcon(defIcon, Resource1.encrypted);
phiconLarge = phicon;
phiconSmall = new Icon(phicon, new Size(16, 16));
}
else if (lngFileSize > 0)
{
phiconLarge = Resource1.decrypted;
phiconSmall = new Icon(Resource1.decrypted, new Size(16, 16));
}
else
{
Icon empty = Icon.FromHandle(ExtractSpecialIcon(defIcon).GetHicon());
phiconLarge = empty;
phiconSmall = new Icon(empty, new Size(16, 16));
}










由上面的程式碼處理後,對於檔案內容起始含有GWGDE字串及有無內容都會呈現出不一樣的檔案圖示。



(3) 註冊要處理的檔案型態



有了以上的處理動作,接下來就是要如何將程式安裝到系統中,當用戶在處理檔案讀寫時,explorer會針對檔案內容判斷後改變檔案圖示。



這個動作牽涉到註冊DLL檔案,必須利用RegAsm.exe註冊工具來註冊DLL,但DLL必須先實作RegisterServer和UnregisterServer來告訴RegAsm要註冊的動作,範例中我只使用.txt的檔案型態做動作



private static void RegTXT()
{
RegistryKey root;
RegistryKey rk;

root = Registry.ClassesRoot;
rk = root.OpenSubKey(".txt");
string txtclass = (string)rk.GetValue("");
if (string.IsNullOrEmpty(txtclass))
{
txtclass = "TXT";
rk.SetValue("", txtclass);
}
rk.Close();

rk = root.CreateSubKey(txtclass +
                 "\\shellex\\ContextMenuHandlers\\" + KEYNAME);
rk.SetValue("
", GUID);
rk.Close();

rk = root.CreateSubKey(txtclass + "
\\shellex\\IconHandler");
rk.SetValue("
", GUID);
rk.Close();

rk = root.CreateSubKey(txtclass +
                  "\\shellex\\{00021500-0000-0000-C000-000000000046}");
rk.SetValue("
", GUID);
rk.Close();
}
 






(4) 標示DLL為強式名稱(strong name)



要成功註冊DLL,必須先將DLL標示成強勢名稱,我們可以利用sn.exe工具來產生金鑰檔案



sn –k Key.snk



再透過專案屬性中的簽署來設定簽署檔案,如此編譯出的DLL就符合標準



clip_image002



(5) 利用RegAsm.exe註冊與註銷DLL



因為註冊工具有分成32和64位元的作業系統版本,所以在使用時要有所區分,我將動作寫在batch檔案中分成reg32.bat和reg64.bat,如果要註銷時須使用unreg32.bat和unreg64.bat。



安裝完後必須重新啟動explorer,使DLL載入;註銷完後也不會立即失效,因為此時只清除登錄資料庫中的資料,但DLL已載入到explorer中所以必須重新啟動explorer才能徹底移除該DLL。



此外因為動作牽涉到登錄資料庫,所以命令提示字元必須以管理員身分開啟,否則會註冊失敗。



(6)



由下面可以看出三個檔案都是文字檔(.txt),但會因為內容不同呈現出不同圖示



2011-06-12_225800