January 22, 2014

The 'Microsoft.VisualStudio.Editor.Implementation.EditorPackage' page did not load correctly

在Visual Studio 2013莫明地crash後,試著重新開啟它,卻一直出現以下錯誤訊息。不論是按下Yes或No,方案內的專案都無法被載入,且關閉VS時還會出現另一個錯誤訊息導致無法關閉,最後僅能透過Task Manager去強制關閉VS。


有國外開發人員也反應在VS 2012遇到一樣的問題,更新了VS 2012 Update 4後問題就不見了。最後我參考了MSDN論壇的討論解決了這個問題。作法是到%LOCALAPPDATA%\Microsoft\VisualStudio\12.0 (例如在我的Windows 8系統下為C:\Users\Pete.chen\AppData\Local\Microsoft\VisualStudio\12.0)路徑下將ComponentModelCache資料夾移除,接下來重新啟動VS便可正常運作。


January 20, 2014

.NET內建的Observer Pattern應用

在一個Windows Forms專案中,有個需求如下。

主表單有一DataGridView顯示會員資料列表,使用者點選某筆會員資料,系統會跳出一個子表單提供修改會員資料功能。更新會員資料後,主表單DataGridView裡的會員資料也隨之更新,以反應最新會員資料。




依上所述,可能的實作方式可以在主表單加入一個取得最新會員資料的方法,如LoadLatestUsers。在子表單中更新會員資料後,呼叫主表單的LoadLatestUsers以更新主表單的顯示資料。

主表單
private void DataGridView_Users_CellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e)
{
    if (e.RowIndex == -1)
    {
        return;
    }

    DataGridViewRow currentRow = this.DataGridView_Users.CurrentRow;

    User user = currentRow.DataBoundItem as User;

    if (user == null)
    {
        return;
    }

    ChildForm form = new ChildForm(user);
    form.Owner = this;
    form.ShowDialog();
}

public void LoadLatestUsers()
{
    this.DataGridView_Users.DataSource = Repository.GetUsers();
}

子表單
public ChildForm(User user)
    : this()
{
    this._user = user;
}

private void ChildForm_Load(object sender, EventArgs e)
{
    this.TextBox_FirstName.Text = this._user.FirstName;
    this.TextBox_LastName.Text = this._user.LastName;
    this.TextBox_PhoneNumber.Text = this._user.PhoneNumber;
}

private void Button_Update_Click(object sender, EventArgs e)
{
    this._user.FirstName = this.TextBox_FirstName.Text;
    this._user.LastName = this.TextBox_LastName.Text;
    this._user.PhoneNumber = this.TextBox_PhoneNumber.Text;
    Repository.Update(this._user);

    MainForm mainForm = this.Owner as MainForm;
    mainForm.LoadLatestUsers();
    this.Close();
}

此一方法主要是在主表單建立一個公開方法LoadLatestUsers,在開啟子表單時,將子表單的Owner設為主表單。如此在子表單更新完資料後,取得Owner轉為主表單型別並呼叫LoadLatestUsers。以此方式實作,主要的缺點就是子表單與主表單耦合度太高,子表單需要明確知道它的Owner是誰。如果它的Owner需要有兩個表單以上該如何處理?

各位也可以發現,上述的需求就如同一般所說的Publisher-SubscriberObserver Pattern。在.NET Framework 4.0開始,內建了兩個介面IObserver<T>IObservable<T>可供開發人員實作Observer Pattern以達到前述的需求。

子表單
public partial class ChildForm : Form, IObservable<User>
{
    private User _user;
    IObserver<User> _observer;

    // 省略

    private void Button_Update_Click(object sender, EventArgs e)
    {
        this._user.FirstName = this.TextBox_FirstName.Text;
        this._user.LastName = this.TextBox_LastName.Text;
        this._user.PhoneNumber = this.TextBox_PhoneNumber.Text;
        Repository.Update(this._user);

        if (this._observer != null)
        {
            this._observer.OnNext(this._user);
        }

        this.Close();
    }

    #region IObservable<User> Members

    public IDisposable Subscribe(IObserver<User> observer)
    {
        this._observer = observer;
        return null;
    }

    #endregion
}

子表單實作了IObservable<User>介面,上面的範例是假設Subscriber只有一個,如果有多個Subscriber可以自行修改,例如將_observer變數改為Dictionary或List型別。在會員資料更新後,呼叫IObserver<T>介面的OnNext方法以通知Subscriber(s)有更新。

主表單
public partial class MainForm : Form, IObserver<User>
{
    // 省略

    private void DataGridView_Users_CellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e)
    {
        // 省略

        ChildForm form = new ChildForm(user);
        form.Subscribe(this);
        form.ShowDialog();
    }

    private void LoadLatestUsers()
    {
        this.DataGridView_Users.DataSource = Repository.GetUsers();
    }

    #region IObserver<User> Members

    public void OnCompleted()
    {
        throw new NotImplementedException();
    }

    public void OnError(Exception error)
    {
        throw new NotImplementedException();
    }

    public void OnNext(User value)
    {
        this.LoadLatestUsers();
    }

    #endregion
}

主表單實作了IObserver<User>介面,並在開啟子表單時,呼叫其Subscribe方法訂閱更新。當子表單更新資料後便會呼叫OnNext方法重新載入最新的會員資料。各位可以看到原本的LoadLatestUser方法已變更為私有方法。OnNext方法傳入的是單一會員資料,如果不想重新載入所有的會員資料,也可以直接更新DataGridView上該筆會員資料以免載入不必要的資料。

以.NET Framework提供的內建介面來實作Observer Pattern,不僅讓子表單與主表單的耦合度降低,若有多個Subscriber,以此方式實作也相當簡單,而子表單也不需要知道它的Subscriber有誰,只管發送更新通知即可,Subscriber自然會作相對應的處理,只要它們都有實作IObserver<User>介面。

完整範例程式碼可至https://github.com/petekcchen/blog/tree/master/DataGridViewObserver下載。

January 16, 2014

工商服務 - Junior/Senior .NET Developer(台北)

公司名稱: Asia Fusion Technology

公司網站: http://www.afusion.com/

工作職缺: Junior/Senior .NET Developer

需求人數: 5人

工作地點: 台北市內湖區瑞光路550號

徵求條件:
  • 三年以上.NET(C#)開發經驗
  • 具ASP.NET MVC開發經驗
  • 會使用jQuery
  • 會使用至少一套測試框架(NUnit/MSTest)
  • 會使用至少一套版本控制工具(Git/SVN)
  • 會使用SQL Server並撰寫T-SQL或Stored Procedure
  • 具物件導向基本觀念
  • 具三(多)層式架構觀念
  • 良好的團隊溝通能力

加分條件:
  • 具Domain Driven Design經驗
  • 具Design Pattern/Dependency Injection/Inversion of Control經驗
  • 具Test Driven Development經驗
  • 具Continuous Integration經驗
  • 具Agile/Scrum經驗
  • 具跨國團隊開發經驗
  • 具英文溝通能力

基本福利制度:
  • 勞保、健保、團保、三節禮金/禮品、周休二日
  • 燈光美氣氛佳,舒適的工作環境,並附有撞球桌及多項健身器材
  • 自由發揮的成長空間
  • 相關課程訓練及進修學習補助、進修假
  • 零食、飲料不定期供應
  • 不定期的聚餐、生日餐會、尾牙活動、電影欣賞、健康檢查等
  • 優於勞基法的年假及請休假制度,包括全薪病假、長輩照護假、幼兒照護假等

薪資待遇: 面議

聯絡人: Jonathan

請將您的中英文履歷和過去自行撰寫過的程式碼參考寄至hrtw@afusion.com

合適者通知面試。面試分兩個階段,第一階段與台北公司Team Lead面試,合適者將與加拿大公司CTO及Architect進行第二階段面試。

January 10, 2014

Debug模式下例外丟出無法被應用程式攔截

在一Windows Forms專案中發現,編譯後的執行檔在執行某個功能時會丟出例外顯示錯誤訊息。試著以Debug模式重現問題,應用程式卻完全不會丟出例外錯誤訊息,但log檔確實有記錄到例外訊息。

加入中斷點後逐步偵錯,才在丟出例外的程式碼停止,但例外訊息只顯示在Output視窗,且沒有詳細資料,就好像被吃掉一樣。


研究了一下發現,在Visual Studio 2010選單Debug-> Exception中,Common Language Runtime ExceptionsThrown欄位是未被勾選的。


在我的例子中,丟出的例外為System.ArgumentOutOfRangeException。可以看到這個項目是沒有被勾選的。


Common Language Runtime ExceptionsThrown欄位勾選,再重新執行一次Debug模式就可以看到應用程式有丟出例外錯誤訊息。


January 7, 2014

列舉資料flattening實作

有一需求要把如下列舉物件資料做flattening後顯示為Pete,Chen,09xx123456;Claire,Chang,09xx234567;Pudding,Chen,09xx345678;Jelly,Chen,09xx4567891。也就是單一筆資料內的屬性是以逗號串接起來,而每筆資料再以分號串接起來。
private IEnumerable<User> Users
{
    get
    {
        yield return new User() { FirstName = "Pete", LastName = "Chen", PhoneNumber = "09xx123456" };
        yield return new User() { FirstName = "Claire", LastName = "Chang", PhoneNumber = "09xx234567" };
        yield return new User() { FirstName = "Pudding", LastName = "Chen", PhoneNumber = "09xx345678" };
        yield return new User() { FirstName = "Jelly", LastName = "Chen", PhoneNumber = "09xx456789" };
    }
}

public class User
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string PhoneNumber { get; set; }
}

要達到上述需求,比較苦力一點的作法就是自已寫一個method,將列舉物件資料傳入後以迴圈方式取出資料後串接,但如果要串接的列舉物件資料越多,就會需要更多相對應的method來處理。另一種方式則是利用LINQ提供的projection功能搭配string.Join來達到串接的效果。

首先我們使用LINQ提供的Select方法來將列舉資料中的物件project成單一字串。
IEnumerable<string> projected = this.Users.Select(c => string.Format("{0},{1},{2}", c.FirstName, c.LastName, c.PhoneNumber));
Assert.AreEqual("Pete,Chen,09xx123456", projected.ElementAt(0));
Assert.AreEqual("Claire,Chang,09xx234567", projected.ElementAt(1));
Assert.AreEqual("Pudding,Chen,09xx345678", projected.ElementAt(2));
Assert.AreEqual("Jelly,Chen,09xx456789", projected.ElementAt(3));
project後,列舉資料的型別就變為string了。接下來只要使用string.Join將列舉資料以分號串接起來即可完成實作。
string joined = string.Join(";", projected);
Assert.AreEqual("Pete,Chen,09xx123456;Claire,Chang,09xx234567;Pudding,Chen,09xx345678;Jelly,Chen,09xx456789", joined);
當然也可以用一行程式碼來完成。
string joined = string.Join(";", this.Users.Select(c => string.Format("{0},{1},{2}", c.FirstName, c.LastName, c.PhoneNumber)));
Assert.AreEqual("Pete,Chen,09xx123456;Claire,Chang,09xx234567;Pudding,Chen,09xx345678;Jelly,Chen,09xx456789", joined);

January 2, 2014

StatusStrip內ProgressBar靠右對齊的小技巧

StatusStrip控制項允許加入ProgressBar控制項來顯示資料存取的進度狀況。ProgressBar加入時,預設是靠左顯示,但有時我們會希望它能靠右顯示。


ProgressBar有個屬性Alignement,在屬性視窗內它的描述為Indicates whether the item aligns towards the beginning or end of the ToolStrip。乍看之下以為設定為Right,ProgressBar就可以靠右對齊,但事實上在StatusStrip的LayoutStyle屬性為Table時,這個設定不會有作用。


大部份建議的作法是在ProgressBar前加上一個空白的StatusLabel,將其Spring屬性設為TrueText屬性設為空白。如此StatusLabel會將ProgressBar擠到最右邊去。但此方法需在StatusStrip的LayoutStyle屬性為Table時才會有作用。


在研究的過程中發現另一個可行的作法是將StatusStrip的RightToLeft屬性設為True,如此ProgressBar便會從右邊開始顯示達到靠右對齊的效果。如果ProgressBar原本右邊有StatusLabel的話,會被移到ProgressBar左邊,可手動調整位置以維持版面配置。