2010年9月29日 星期三

讓 Visual Studio 跑 3GB

緣由

在許多狀況,Visual Studio 都會跑的很辛苦,其中一個原因是 Visual Studio 只有 32位元的版本,而沒有 64 位元的版本。至於為什麼微軟不出 64 位元版本,原因很多,詳情見Visual Studio: Why is there no 64 bit version? (yet)

然而,Windows 只能讓32位元的應用程式存取到 2GB的記憶體。對於剛推出32位元電腦的時代(15年前了吧),有4MB記憶體的電腦就已經很高檔了。 對於現在的我來說,我的電腦已有8GB,作業系統也是Windows 7 64位元版,Visual Studio 只能跑2GB 的限制簡直是孫悟空的緊箍咒,真是天大的限制。尤其是跑  CAT.NET 時,需要大量的記憶體,2GB 一下子會出現 out of memory,真是不知道該怎麼辦。

[解法一]

找到了這一篇 Hacking Visual Studio to Use More Than 2Gigabytes of Memory。讚!

由於我是 64位元的系統,對於系統的 3GB修改就免了。直接使用 Visual Studio Command Prompt,執行下面的指令

cd "C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE"editbin /LARGEADDRESSAWARE devenv.exe

執行

以下執行 CAT.NET 時,需要2.05 GB 的記憶體。在未操作前,跑到 1.64 GB 就不行了。

image

[解法二]

OutOfMemoryException Fix 這裡則給了另一個解法。由於這是 ReSharper 的公司 jetbrains 給的 solution,但在 windows server 2008R2 + Visual studio 2005 上跑似乎並非預期可以解決該問題。

[20101001] 在 Windows Server 2003 + Visual studio 2005,該方法卻似乎有用,但在最後,還是讓 Visual Studio 2005 掛掉。

image

Windows Server AppFabric: ASP.NET 4 應用程式共用 Session

常常被問到,ASP.NET 的應用程式是否可以共用 Session?我之前的答案一律是「不行」,但真的不行嗎?其實是可以的,只是很難實作。現在,應用 Windows Server AppFabric 的 Cache 功能,就可以達到這樣的境界。

環境

使用上次介紹的建立 AppFabric Cache Cluster,就可以建立起 Windows Server AppFabric 的環境。

測試網頁

首先,建立一個設定/讀取 Session 的網頁。網頁內容如下。

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
    CodeBehind="Default.aspx.cs" Inherits="WebApplication3._Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>
        Welcome to ASP.NET! SessionId = <%= Session.SessionID%>
    </h2>
    Set value:
    <asp:TextBox runat="server" ID="txtValue" />
    <asp:Button Text="Set Value" runat="server" ID="btnSet" 
    onclick="btnSet_Click" />
    <br />
    Read value:
    <asp:Label Text="text" runat="server" ID="lblValue" />
    <asp:Button Text="Read Value" runat="server" ID="btnRead" 
        onclick="btnRead_Click" />
    <br />
</asp:Content>

而該頁的 CodeBehind 程式如下

using System;

namespace WebApplication3
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            
        }

        protected void btnSet_Click(object sender, EventArgs e)
        {
            Session["value"] = txtValue.Text;
        }

        protected void btnRead_Click(object sender, EventArgs e)
        {
            lblValue.Text = (string)Session["value"];
        }
    }
}

很簡單的程式。按「Set Value」鍵時,將值寫到 Session中,而按「Read Value」鍵時,將值顯示到網頁上。

在 IIS 上,該Application 的名稱為 WebApplication3。以建立 Application 的方式,建立出使用相同路徑但不同應用程式名稱的 WebApplication4。如下圖

image

未使用 AppFabric 的 Cache 功能時

先打開 WebApplication3 的應用程式 Default.aspx 頁。輸入值為 aaa,再按 Read Value 鍵後,網頁會顯示剛剛輸入的值 aaa。

image

再打開 WebApplication4的網頁 Default.aspx 頁,按Read Value 鍵,網頁顯示值為空白。

image

請注意到,即使用相同的Application,不同的 SessionId代表不同的 session,當然會讀到不同的值。雖然是不同的 Application,但有可能有相同SessionId。為了讓測試有意義,此網頁故意將 SessionId 顯示出來。

使用 AppFabric 的 Cache 作為 ASP.NET 的 Session

將 Web.config 修改如下

<?xml version="1.0"?>
<configuration>
    <configSections>
        <section name="dataCacheClient"
                 type="Microsoft.ApplicationServer.Caching.DataCacheClientSection,
                        Microsoft.ApplicationServer.Caching.Core, Version=1.0.0.0, 
                        Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                 allowLocation="true"
                 allowDefinition="Everywhere"/>
    </configSections>
    <dataCacheClient>
        <hosts>
            <host name="AppFabric1" cachePort="22233"/>
        </hosts>
    </dataCacheClient>
    <system.web>
        <compilation debug="true" targetFramework="4.0" />
        <sessionState mode="Custom" customProvider="AppFabricCacheSessionStoreProvider">
            <providers>
                <add
                    name="AppFabricCacheSessionStoreProvider"
                    type="Microsoft.ApplicationServer.Caching.DataCacheSessionStoreProvider"
                    cacheName="session" sharedId="MyApp"/>
            </providers>
        </sessionState>
    </system.web>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true"/>
    </system.webServer>
</configuration>

此後,如上一測試網頁動作再執行一次。發現可以共用 Session 了。

image

image

注意一下,sharedId="MyApp"/ 兩個應用程式都使用相同的 sharedId。如果不同,就是不同的 session 了.

結論

使用 Windows Server AppFabric 的 Cache 作為 ASP.NET 的 Session,除了享有高可用性(High Avaliability)外,也意外地擁有跟應用程式共用 Session 的效果。

但,這樣好處的前提是:需要同一個Session。如果使用打開兩次 Internet Explorer,甚至不同的 browser,就很容易看不到同一個 session 了。

範例程式下載

2010年9月28日 星期二

建立 AppFabric Cache Cluster

當應用程式使用了Windows Server AppFabric 的快取(Cache)後,為了服務不被中斷,我們通常會被要求高可用性(High availity)的實作。這一次的分享,我就來簡單地實作一下 AppFabric Cache Cluster並且應用在 ASP.NET 的 Session上。

環境

這一次,我們需要四台機器。一台 SQL Server 2008(電腦名稱為SQL2008SP1), 兩台 Windows Server 2008 R2作為 AppFabric,一台Windows 7安裝VS2010作為開發機器。

用途 電腦名稱
資料庫伺服器 SQL2008STDSP1
AppFabric AppFabric1
AppFabric AppFabric2
Windows 7 WinDev

此環境需要 AD domain,因此全部的機器都需要加入domain。電腦名稱可以自行更名,上表的命名為我的習慣。

AppFabric 的安裝

第一台機器命名為 AppFabric1。步驟大致同之前介紹的安裝過程。不過,重要的是需要指定資料庫伺服器到SQL2008SP1,資料庫名稱AppFacbricCaching。由於這是第一台AppFabric,需要指定為 New Cluster(預設)。

image

image

而第二個 AppFabric2 的安裝是一模一樣的,但必須選 “Join Cluster”

image

建立Cache

這裡需要以管理員的權限來執行 Caching Administration Windows PowerShell

New-Cache –CacheName session

設定 Web.Config

在 ASP.NET 上,我們需要設定 web.config。

第一段需要註冊 config section

<configSections>
<section name="dataCacheClient"
         type="Microsoft.ApplicationServer.Caching.DataCacheClientSection,
                Microsoft.ApplicationServer.Caching.Core, Version=1.0.0.0, 
                Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         allowLocation="true"
             allowDefinition="Everywhere"/>
</configSections>
第二段增加 dataCacheClient 的設定
<dataCacheClient>
    <hosts>
        <host
             name="AppFabric1"
             cachePort="22233"/>
    </hosts>
</dataCacheClient>

第三段則將 sessionstate 改用自訂的 session provider

<sessionState mode="Custom" customProvider="AppFabricCacheSessionStoreProvider">
    <providers>
        <add
            name="AppFabricCacheSessionStoreProvider"
            type="Microsoft.ApplicationServer.Caching.DataCacheSessionStoreProvider"
            cacheName="session" sharedId="MyApp"/>
    </providers>
</sessionState>
注意到 cacheName 使用在上一個指令新增的 Cache “session”

Application Pool 的 Identity

最後,在 IIS 上必須讓該ASP.NET 應用程式跑在一個帳號上,而該帳號必須是資料庫AppFacbricCaching的 owner。

SNAGHTML3725fb

執行

我使用的是 Hyper-V ,很容易地可以暫停/繼續電腦。試著將其中一台電腦暫停,並不會影響 ASP.NET 的運作。

image

2010年9月21日 星期二

Spam Email

最近垃圾信愈來愈多,當然一看就刪除。其中有一封假裝是我寄出去的信被退件,內容如下。

SNAGHTML19b2b47

還蠻像的。一看zip 的內容,果然內含 .exe 檔,想騙我執行它,門都沒有。

image

不過說實話,不熟電腦的人,會不會去執行它呢?100個人中有1個去執行它的話,這種技倆就相當有效了。

IE 9 Beta 上無法使用 Project Server 2003 來回報工時

IE 9 Beta 的效能的確相當快。但畢竟是 Beta 版,難免有許多的 bug,造就一些奇特現象。

我們公司是使用 Project Sever 2003 來管理專案進度。回報專案工時是專案成員的每日工作之一。無奈,一旦安裝了 IE 9 Beta,一進到「任務」頁,網頁就被迫回到首頁,相當莫名其妙。

解法呢?正確的解法當然是等 IE 9 修正這個 bug 了。而目前我得到的暫時解法,是點選按「文件」頁,再點選「任務」頁,就ok 了。

image

真是怪。

This problem will occur when running in 64 bit mode with the 32 bit Oracle client components installed

緣由

開發 Oracle 專案時,發生了下面的錯誤訊息

Attempt to load Oracle client libraries threw BadImageFormatException.  This problem will occur when running in 64 bit mode with the 32 bit Oracle client components installed. Inner exception message: An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B)

環境

以下是我的開發環境

解決方法

    由於 ODAC 目前只有32 bits的程式,而VS2010方案(solution)指定Build Target 是 AnyCPU,在 IIS 7 上就會執行 64 bits。在 64 bits 的 Process 上載入 32 bits 的 Oracle client library 就會發生這樣的錯誤訊息。

   解決方式也很簡單,就是在 IIS 7 上指定 Application Pool 可以跑 32 bits 的程式。

  1. 找到執行該程式的 Application Pool. 我的例子是使用預設的 ASP.NET v4.0。
  2. 在右方的 Action Panel 上,點擊「Advanced Settings」
  3. 將 Enable 32-Bit Applications 設為 True

   image

結論

    此方法只適用在開發機器上,目的是為了使用 Oracle Developer Tools For Visual Studio。在正式機上,就應該安裝 64 bits 的 Oracle Client library.

2010年9月16日 星期四

IE 9 的效能

各家廠牌最喜歡比效能了。我也來試一試。

圖形

這一點真的沒話說,IE 9 完全勝出。由微軟提供的測試頁Speed Read,分別以 IE 9 beta 與 Firefox 4 beta 測試,IE 9 得到的 FPS 為30左右,而 Firefox 4 beta 則只有 5。天差地遠。

Javascript

我使用 Sun spider 的 benchmark 來測試。IE 9 跑完所有 javascript 需要558.2 ms,而 Firefox 4 需求 697.4ms。嗯,IE 9 再勝出。

image

CSS

http://www.howtocreate.co.uk/csstest.html 來測兩個牌子在 CSS 讀取 div 的效能。IE 9 約 16ms,而 Firefox 為10ms。在 CSS selector 的效能上 Firefox 勝出。

使用http://tools.css3.info/selectors-test/test.html 來測時,則兩者時間差不多,也都通過了測試。

ACID3

ACID3 是用來評量瀏覽器是否符合標準的指標。http://acid3.acidtests.org/ 來測試,IE 9 Beta 得到 95 分,而 FireFox 4 Beta 得到 97 分。差異雖不大,但 FireFox 勝出。

結論

雖然兩者互有勝負,但在圖形處理上,IE 9 實在勝太多了。目前來說,IE 9 整體表現較佳。

IE 9 Beta 下載及安裝

微軟的 IE 9 Beta 已經可以下載安裝了。迫不及待地,就裝了起來。

下載

http://www.beautyoftheweb.com ,一路跟著Download 的連結,最後根據我的Windows 版本我下載了一個2.37 MB 的IE9-Windows7-x64-enu.exe 安裝檔。哦!好小。實在出乎意外。

安裝

執行安裝程式,過程只有一個單純的視窗。

image

安裝完畢後,照慣例會要求重開機。

image

初體驗

重開機後,就發現了 IE9 更貼心的設計。超大的內容顯示區!所有的功能表,網址列,分頁等非網頁內容,全都最小化。上網本來就是看內容,誰希望這些非內容的區塊佔據我的視線呢?讚!

image

另外有一個小東西讓我注意到了。在內容區視窗的下方出現了這個提示。

image

打開之後,更貼心的設計出現了。它統計了 IE 啟動時各個 Add-On 所花的時間。我的 Add-On 最花時間的是 Search Helper,花了我 0.37 秒。要停掉嗎?先不要好了,反正全部加起來也不到 0.5 秒。 IE 9 希望所有的 Add-On 的啟動時間加起來不到 0.2秒,對我來說真是嚴苛啊。

image

超快速度

真的如之前的傳言,IE 9 超快。Yahoo 奇摩的首頁目前算是挺複雜的了,竟也不到1秒就顯示出來。如果重新整理的話,整個畫面就像瞬間跳出來一樣。真是不同的體驗。

搜尋輸入框

有個重要的搜尋輸入框不見了。天啊, IE 8 最常用的搜尋,到了 IE 9 竟然被拿掉,這怎麼可以呢?一定要告…。等一下,原來直接打在網址列就可以了。這樣一來,又少了一個佔據視窗的東西。

image

結論

初體驗的經驗真是棒極了。雖然只有短短的半小時,就讓我訝異連連。看來 IE 9 真的是要大反攻了!現在就開始使用吧。

2010年9月7日 星期二

Visual Studio 2010 無法連線到 Visual Source over internet

正常來說,Source Control 改使用 Visual SourceSafe (Internet)後,在打開專案的視窗中,應該可以看到 Add SourceSafe Database 的項目。

然而,我的同直卻空空如也,一片空白。為什麼呢?原來又有更新未上。見 http://support.microsoft.com/kb/943847/en-us?p=1

 

SNAGHTML1b6bb2f

2010年9月5日 星期日

WPF 學習:Static Binding

WPF 的強項當然要說到 Biding 了。然而 Data Binding 在 .NET 平台上到處可見。WPF 的 Binding 到底有什麼特別的呢?

下面是一個重構的過程。由最開始由未使用 Binding,一直到使用 Binding 完成後,您可以見到該 Binding 的威力。

需求

身為一個使用者,我希望可以自行挑選表單的背景顏色

第一版:未 Binding

第一個版本是類似 Windows Form 的實作方式。首先打開 Visual Studio 2010, 建立一個 WPF 的應用程式。建立專案後,開啟 MainWindows.xaml,拖進一個 ListBox,並命名為 ColorListBox。如下圖

image

在 Code Behind 的 MainWindows.xaml.cs 上,將程式MainWindows 的建構子修改如下

public MainWindow()
{
    InitializeComponent();

    var props = typeof(Brushes).GetProperties();
    var q = from p in props
            select new
            {
                Name = p.Name,
                Brush = (Brush)p.GetValue(null, null)
            };
    ColorListBox.ItemsSource = q;
    ColorListBox.SelectedValuePath = "Brush";
    ColorListBox.DisplayMemberPath = "Name";
}

在 MainWindow 的建構子中,已經使用了 DataBinding 技術,如同 Windows Form 相同。接下來在 ColorListBox 上雙擊左鍵滑鼠兩下。並修改 EventHandler 如下

private void ColorListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    this.Background = ColorListBox.SelectedValue as Brush;
}

到這裡,程式已經可以運作,而且程式碼相當乾淨。 Code Review 時,見到這樣的程式已經要感謝上天了。

程式運作起來,如下圖.

image

第二版:重構,建立一個 NamedBrush

第一版的程式可個缺點:建構子太長,而且內含非 UI 的邏輯,即使用 Reflection 讀取所有 Brushes 的 public property。
這一個重構的目的,就是將這一段程式拆出來。改放到一個 NamedBrush 類別。

using System.Linq;
using System.Windows.Media;

namespace WpfApplication2
{
    public class NamedBrush
    {
        public Brush Brush { get; set; }
        public string Name { get; set; }
        public static NamedBrush[] All { get; set; }

        static NamedBrush()
        {
            var props = typeof(Brushes).GetProperties();
            var q = from p in props
                    select new NamedBrush
                    {
                        Name = p.Name,
                        Brush = (Brush)p.GetValue(null, null)
                    };
            All = q.ToArray();
        }
    }
}

而 MainWindow.xaml.cs 就可以簡化如下

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        ColorListBox.ItemsSource = NamedBrush.All;
        ColorListBox.SelectedValuePath = "Brush";
        ColorListBox.DisplayMemberPath = "Name";
    }

    private void ColorListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        this.Background = ColorListBox.SelectedValue as Brush;
    }
}

第三版:在xaml中設定屬性

為了簡少Code behind 的程式,我們將建構子中的 ColorListBox 的屬性值設定陳述句移到 xaml 中。 也就是將下面這兩行

ColorListBox.SelectedValuePath = "Brush";
ColorListBox.DisplayMemberPath = "Name";

移到 xaml 中,如下

<ListBox Height="213" HorizontalAlignment="Left" Margin="76,12,0,0" Name="ColorListBox" VerticalAlignment="Top" Width="353"
  SelectedValuePath="Brush" DisplayMemberPath="Name"/>

 

第四版:Binding

WPF 特有的 Binding 可以取代一些 Event Handler,如同在WPF 學習:Binding中已經使用過這樣的技巧。因此, ColorListBox_SelectionChanged 這樣簡單的有一些可以移到 xaml 中。

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" Background="{Binding ElementName=ColorListBox, Path=SelectedValue}">

意思是:Window 的 Background 值繫結來至 ColorListBox 的 SelectedValue。

到了這個步驟,整個 Code behind 只剩下

using System.Windows;

namespace WpfApplication2
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            ColorListBox.ItemsSource = NamedBrush.All;
        }
    }
}

換句話說,只剩一行的 ColorListBox.ItemsItemsSource = NamedBrush.All; 這一行程式要寫。其他的都移到了 xaml中。

第五版:使用靜態 Binding

雖說已經簡化到了不行,剩下的最後一行,難到無計可施嗎?WPF 還有一招:StaticBinding。在 xaml 宣告一個 Window Resource 如下

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:WpfApplication2"
        Title="MainWindow" Height="350" Width="525" Background="{Binding ElementName=ColorListBox, Path=SelectedValue}">
    <Window.Resources>
        <l:NamedBrush x:Key="NB" />
    </Window.Resources>

注意到我增加了 xmaln:l=’clr-namespace:WpfApplication2’ 這一行 namespace 宣告。

最後,在 ColorListBox 的 ItemsSource 屬性設定如下

<ListBox Height="213" HorizontalAlignment="Left" Margin="76,12,0,0" Name="ColorListBox" VerticalAlignment="Top" Width="353"
                 SelectedValuePath="Brush" DisplayMemberPath="Name" 
                 ItemsSource="{Binding Source={StaticResource ResourceKey=NB}, Path=All}"/>

這樣一來,Code Behind 的程式就與最初的相同了。

using System.Windows;

namespace WpfApplication2
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

討論

為什麼我們汲汲營營的要把 xaml.cs 的程式降到0行客製程式呢?第五版的步驟,為了將一行的建構子程式,我們修改了 xaml 三個地方,這樣划的來嗎?

其實,這樣做有兩大好處。第一項好處,是沒有 Code behind 的程式後,美工的部份可以完全交給設計人員。設計人員只需使用 Expression Blend 來設計樣式,完全不必懂程式如何寫作,就可以使用 Binding 的方式設計行為。

第二項好處,是UI 的資料(Model)完全分離,這有利於單元測試。對於容易進行測試的 NameBrush 類別進行單元測試,而難以進行測試,又常常改化的 UI就可以進行 Coded UI Test 了。

範例程式下載

2010年9月3日 星期五

WPF 應用程式,App.xaml 不使用 StartupUri 時無法套用 ResourceDictionary

前言

WPF 實在是很棒的一個 Framework,而且在開發設計時,Visual Studio IDE 的輔助,讓我們的開發更具生產力。但也因為如此,當我們不朝預設的開發方式時,就容易遇到一些「地雷」。今天我就踩到一個。

問題

問題的由來是有演變過程的。以下就一步步的演進吧。

步驟1: 標準的 Style

我按標準步驟產生了一個 WPF Application後,在 MainWindow.xaml  上放了一個 Button。

image

Xaml 如下, 非常簡單

<Window x:Class="WpfApplication2.MainWindow"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                Title="MainWindow" Height="350" Width="525">
        <Grid>
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="40,36,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
    </Grid>
</Window>

 

 

步驟2: 套用標準的 Style 到每一個 Button

為了讓整個程式的按鍵看來有一致的外觀,我在 App.xaml 上增加了 Button Style。App.xaml 如下,也非常的 easy

<Application x:Class="WpfApplication2.App"
                         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                         StartupUri="MainWindow.xaml">
        <Application.Resources>
        <ResourceDictionary>
            <Style TargetType="Button">
                <Setter Property="Background" Value="LightBlue" />
            </Style>
        </ResourceDictionary>
    </Application.Resources>
</Application>

而無論在開發階段或執行階段,Button 的樣式果然成我預期的背景顏色LightBlue 淺藍色。

image image

步驟3:移除 StartupUri

因故,我必須移除 App.xaml 中的 StartupUri,並自行建立 MainWindow 的 instance。修改後的 App.xaml如下

<Application x:Class="WpfApplication2.App"
                         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                         > <!--注意到我移除了 StartupUri-->
        <Application.Resources>
        <ResourceDictionary>
            <Style TargetType="Button">
                <Setter Property="Background" Value="LightBlue" />
            </Style>
        </ResourceDictionary>
    </Application.Resources>
</Application>

而 App.xaml.cs 如下

using System.Windows;

namespace WpfApplication2
{
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            var mainWindow = new MainWindow();
            mainWindow.Show();
        }
    }
}

程式可以執行,但問題來了:執行時期,Button 的樣式跑掉了。

image

這是怎麼一回事?開發的Designer 仍然是淺藍色背景--我要的樣式啊!

原因

原因是:開發工具太強了,幫我們做了太多的事。將原來的 StartupUri 加回去,並在 Solution Explorer 下找到 obj\Debug\ 可以找到一堆由 Visual Studio 幫我們產生的程式碼。

image

打開 App.g.i.cs ,可以找到 InitializeComponent 這個方法。可以看到它是如何幫我們自動載入 Resource 的.

image

一旦我們將 StartupUri 移除掉,這段程式就會變化。整個 InitializeComponent 方法就不見了!!

這就是原因所在。

解決

既然找到了原因,當然就知道要怎麼解了。方法有二。

第一個方法是自己補上消失的程式,並且自己將消失的 style 補上。這個方法難度較高,而且不好維護。我採用第二個方法:自訂一個 ResourceDictionary。

新增一個 ResourceDictionary 並命名為 AppResource.xaml, 並將原來在 App.xaml 的 style 移到這裡。

SNAGHTML8e2c6c

AppResource.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style TargetType="Button">
        <Setter Property="Background" Value="LightBlue" />
    </Style>
</ResourceDictionary>

改用 。修改App.xaml 如下

<Application x:Class="WpfApplication2.App"
                         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                         > <!--注意到我移除了 StartupUri-->
        <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="AppResource.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

然後,執行時期的 Button 樣式就回來了。

image

結論

WPF 實在設計的很棒,而 IDE 的開發輔助也讓我們快速的完成常見的需求。只是在 IDE 快速開發的背後,我們常常忽略了原來應該是我們要做的事。一旦不符合原 IDE 的快速開發條件時,就摸不著頭緒了。

Share with Facebook