September 27, 2013

WiX中加入.NET Framework版本檢查

WiX來打包安裝檔(如*.msi)時,常會希望安裝過程中可以先檢查主機環境是否符合特定需求再進行安裝,例如.NET Framework版本是否能執行欲部署的應用程式,如果不符則顯示警告訊息給應用程式使用者並中止安裝。

在WiX中若要進行.NET Framework版本的檢查,需要借助WiX所提供的延伸函式庫WixNetFxExtension.dll。我的作業系統環境為Windows 7 Professional x64 SP1,安裝的WiX Toolset版本為3.7,預設安裝路徑為C:\Program Files (x86)\WiX Toolset v3.7,延伸函式庫則存放在bin資料夾下。

以下為設定步驟

將WixNetFxExtension.dll加入參考


上圖為SharpDevelop上開啟WiX專案的畫面,目前我是以SharpDevelop來編輯WiX設定檔。


設定檔中根節點Wix中加入命名空間xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension",如
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
  xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension">


Wix/Product/Package節點後加入以下兩個節點,如下為檢查.NET Framework 4.0是否已安裝在系統上,NETFRAMEWORK40FULL代表檢查的是.NET Framework 4.0版本,詳細可使用的版本列表可參考WixNetfxExtension;而Installed則代表檢查是發生在安裝MSI檔時而非修復或移除時。
<PropertyRef Id="NETFRAMEWORK40FULL"/>
<Condition Message="請先安裝Microsoft .NET Framework 4.0再安裝此軟體">
    <![CDATA[Installed OR NETFRAMEWORK40FULL]]>
</Condition>

例如
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension">
 <Product Id="8CB51EE0-0F1C-4185-90BC-11B865F4CFD5" Name="Demo Application" Language="1028" Codepage="950" Version="1.0.0.0" Manufacturer="Pete Chen" UpgradeCode="A887890D-18A1-437A-95D4-0836AC792366">
  <Package InstallerVersion="200" Compressed="yes" Languages="1028" Manufacturer="Pete Chen" Description="Demo Application" SummaryCodepage="950" />
  <PropertyRef Id="NETFRAMEWORK40FULL"/>
  <Condition Message="請先安裝Microsoft .NET Framework 4.0再安裝此軟體">
      <![CDATA[Installed OR NETFRAMEWORK40FULL]]>
  </Condition>

這邊要注意的是,PropertyRef及Condition節點一定要放在Package節點之後,否則無法建置WiX成功,因為根據XSD (C:\Program Files (x86)\WiX Toolset v3.7\doc\wix.xsd)的定義,Product節點後的第一順位為Package節點。


設定完成後建置出MSI檔並安裝在無安裝.NET Framework 4.0環境的主機,可以看到系統會出現警告視窗並停止安裝動作。



延伸閱讀


September 24, 2013

兩款WiX編輯器 - WiX Edit與SharpDevelop

WiX是一套用來封裝應用程式檔案成安裝檔(*.msi)的工具,它提供Visual Stuio專案樣板和編譯工具,讓開發人員在Visual Studio編輯完WiX所支援的XML格式設定檔(*.wxs)後,可以透過建置功能打包應用程式檔案。在前一間公司任職時,也是使用WiX並整合至CI Server進行自動化封裝。然而在設定上,WiX主要還是透過編輯XML設定檔來完成,相當不親民。


封裝機制其實VS也有內建專案樣板可以使用,也提供精美的編輯畫面,小巧好用且操作不難,我在最近一個小型的Windows Forms專案中,也嘗試使用這內建的封裝功能,以避免XML檔案編輯地獄,一開始效果還不錯,當要給測試人員測試時,我只要編譯一下方案檔來產出安裝檔送出去就好了。但在我試著把它整合至CI Server(Jenkins)中時卻發現MSBuild工具無法編譯*.vdproj專案檔,雖然有國外網友提出使用devenv.exe對整個方案檔做編譯來解決這個問題,但因為不打算在CI Server上安裝VS,所以我又回到了使用WiX。


以上全是題外話,和主題無關。既然再回到WiX的懷抱,就要想辦法爬出XML編輯地獄,所以找到了兩款WiX編輯器來輔助開發。

WiXEdit

WiXEdit是一套免費的WiX編輯器,目前的版本為0.7.5,不過它已經兩年多沒發佈新版本了。透過WiXEdit提供的GUI,可以輕鬆地選擇要加入封裝的檔案或資料夾。不過在使用上發現有個小缺點,就是右側的屬性是要自己手動去選擇新增。



SharpDevelop

SharpDevelop事實上不只是一套WiX編輯器,而是一套完整又開源的.NET開發工具,且目前仍持續在更新。相較於WiXEdit,SharpDevelop的GUI較為精美,操作上相對流暢,也不用自行新增屬性,SharpDevelop會自動載入可以設定的屬性名稱。


SharpDevelop也有內建的WiX專案樣板,如果不想在VS編輯WiX設定檔,也可以直接在SharpDevelop建立WiX專案開發。有興趣的朋友可參考WiX Integration這篇文章在SharpDevelop上建立自己的WiX專案,雖然文章已是七年前但設定沒有太大的差別。


這兩套工具我並沒有玩得很深入,因為目前的需求只要能把檔案封裝成MSI檔即可,所以基本上使用到的功能只有加入檔案或資料夾、設定桌面捷徑及開始選單捷徑、移除程式捷徑、網頁捷徑等。諸如安裝畫面客製化,安裝前後執行特定指令等功能尚未接觸到。

September 11, 2013

ToolTip Control in Windows Forms

Tooltip is often used as a hint or helper to tell application users what the functionality of a specific control is for or what will happen when they conduct operation on those controls, for instance, click a button.

In Windows Forms, you cannot set up the tooltip for a control until you drag a ToolTip control into the designer (you can create a ToolTip instance in the code though) and put your tooltip in the ToolTip on [ToolTip Control Name] property.




That being said, in some cases you will need to set up your tooltip PROGRAMMATICALLY. For example, in my case I want to display the tooltip on a button indicating a file path from a data source when the cursor hovers the button. We can leverage the ToolTip.Show method of the ToolTip control to assign the message being displayed to the button.
private void Button_OpenStonePhoto_MouseHover(object sender, EventArgs e)
{
    this.ToolTip_Photo.Show(@"C:\Users\Pete\Desktop\Pete.jpg", this.Button_OpenStonePhoto);
}

Alternatively, you can use the ToolTip.SetToolTip method to achieve the same, but note that the two methods have subtlety based on MSDN.
private void Form1_Load(object sender, EventArgs e)
{
    this.ToolTip_Photo.SetToolTip(this.Button_OpenStonePhoto, @"C:\Users\Pete\Desktop\Pete.jpg");
}

September 7, 2013

書評 - AOP in .NET: Practical Aspect-Oriented Programming

圖片來源:天瓏網路書店
Aspect-Oriented Programming (AOP)這名詞我大概是在兩三年前看過幾篇介紹文章知道的,但再更早之前AOP就已被提出來了。AOP主要是用來解決撰寫Cross-Cutting Concern時會遇到code duplication及混淆business logic等問題,如Logging、Caching、Validation等。在Java領域中,很早就有專書講解AOP在Java上的應用,而在.NET領域中雖然介紹文章不算少,尤其是隨著ASP.NET MVC的熱門,AOP在.NET的應用更為關注,但專門討論AOP的書這本應該算是第一本,而在它出版前我也注意它有一段時間。

本書除了介紹AOP的專有名詞及概念外,直接以兩套知名的.NET AOP Framework做為範例來介紹在.NET中如何實作AOP。這兩套Framework分別是Castle DynamicProxyPostSharp。這兩套的差別在於DynamicProxy是在run-time透過Proxy物件執行AOP,且DynamicProxy主要是和IoC container搭配實作;而PostSharp是在compile-time時由framework做IL weaving將Advice插入原始程式中。DynamicProxy是免費開源的,而PostSharp有分免費版和付費版。此外,這本書只有近300頁,搭配程式碼範例讀起很輕鬆也容易吸收。

我在最近的一個Windows Form專案中也參考了這本書使用PostSharp加入AOP的設計,讓Cross-Cutting Concern和Business Logic分離,程式碼精簡不少,Business Logic也乾淨了許多。如果有朋友想了解AOP,我相當推薦這本書做為參考。

September 5, 2013

Two-way binding in Windows Forms

Recently I've been involved in a small-scale project using Windows Forms, and I wondered if there's two-way binding implementation in Windows Forms, just kind of like what Knockout.js does in a web application.

Fortunately I do find what I wanted in Windows Forms. Controls such as TextBox, Combobox and others have a property called DataBindings which can help achieve two-way binding. I'm going to give a brief example below.

Here we have a view model, say, EmployeeViewModel, consisting of three properties.
public class EmployeeViewModel
{
    public string FirstName
    {
        get;
        set;
    }

    public string LastName
    {
        get;
        set;
    }

    public string PhoneNumber
    {
        get;
        set;
    }
}
Here's the form. We have three TextBoxes and two Buttons in the form. The three textboxes correspond to FirstName, LastName and PhoneNumber property in the EmployeeViewModel. The "Show me the view model" button will print out the values of the properties of the view model in the output window in Visual Studio, whereas the "Modify the view model" button will update the view model with hard-coded values.


In the form load event, we register the bindings between the Text property of texboxes and the FirstName/LastName/PhoneNumber property of EmployeeViewModel.
private void Form1_Load(object sender, EventArgs e)
{
    this.TextBox_FirstName.DataBindings.Add("Text", this._employeeViewModel, "FirstName");
    this.TextBox_LastName.DataBindings.Add("Text", this._employeeViewModel, "LastName");
    this.TextBox_PhoneNumber.DataBindings.Add("Text", this._employeeViewModel, "PhoneNumber");
}

Here's what the "Show me the view model" button does in the code. You can see that the code tries to print out the values of the properties of the view model in the output window.
private void Button_ShowMeTheViewModel_Click(object sender, EventArgs e)
{
    Console.WriteLine("First Name: " + this._employeeViewModel.FirstName);
    Console.WriteLine("Last Name: " + this._employeeViewModel.LastName);
    Console.WriteLine("Phone Number: " + this._employeeViewModel.PhoneNumber);
}

Compile the application and run it first. When you type in your first name, last name and phone number in those textboxes and click "Show me the view model", you see the magic. What does that mean? That means that you don't even need to grab values from the textboxes using TextBox.Text. You just get the values from the view model and print them out. With the binding, the modification on the textboxes will also apply to the view model.


Until now, I've actually achieved only one-way binding. What's the other "way"? Look at the code in the "Modify the view model" button.
private void Button_ModifyTheViewModel_Click(object sender, EventArgs e)
{
    this._employeeViewModel.FirstName = "Claire";
    this._employeeViewModel.LastName = "Chang";
    this._employeeViewModel.PhoneNumber = "+886928xxxxxx";
}

That simple? I just modify the view model and give it some hard-coded values. Hold on, what kind of result would I expect from just updating the view model? I would hope that when the values of the view model get changed, the values of textboxes will update accordingly. Let's compile and run the application again, then click "Modify the view model".


Oops! Nothing seems happened. Yes, you see nothing changed in the textboxes. In order to notify textboxes of the change in EmployeeViewModel, you will need to implement the INotifyPropertyChanged interface. Here's the code snippet for updated EmployeeViewModel that implements INotifyPropertyChanged.
public class EmployeeViewModel : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private string _firstName;

        public string FirstName
        {
            get
            {
                return this._firstName;
            }
            set
            {
                if (this._firstName != value)
                {
                    this._firstName = value;
                    NotifyPropertyChanged("FirstName");
                }
            }
        }

        private string _lastName;

        public string LastName
        {
            get
            {
                return this._lastName;
            }

            set
            {
                if (this._lastName != value)
                {
                    this._lastName = value;
                    NotifyPropertyChanged("LastName");
                }
            }
        }

        private string _phoneNumber;

        public string PhoneNumber
        {
            get
            {
                return this._phoneNumber;
            }
            set
            {
                if (this._phoneNumber != value)
                {
                    this._phoneNumber = value;
                    NotifyPropertyChanged("PhoneNumber");
                }
            }
        }
    }

Now run the application again then click "Modify the view model". You should see the magic again.


With two-way binding, you focus on your model or view model most of the time. When you update your controls, your model gets modified too and vice versa. However, registering the DataBindings for controls could be error-prone since you will have to hard-code the property name in your code as string. When you refactor the property names in the model in Visual Studio, those hard-coded property names will not be refactored accordingly. What's more, if you have a huge view model, you will to some degree have code duplication because of the INotifyPropertyChanged implementation. With the help of an advanced AOP tool like PostSharp, you can reduce the duplication gracefully. I'm not going to talk about AOP or PostSharp here because it's out of scope of this post.