2012年1月2日

WPF Styles


Sharing Styles

可以把不同 element property <Style x:Key=”…”> 集中在一起,如果 element 沒有這項 property,則會被忽略。
Example:
<StackPanel>
    <StackPanel.Resources>
        <Style x:Key="controlStyle">
            <Setter Property="Control.FontSize" Value="15"/>
            <Setter Property="Control.Height" Value="25"/>
            <Setter Property="Control.Width" Value="100"/>
            <Setter Property="Control.HorizontalAlignment" Value="Left"></Setter>
            <Setter Property="Control.VerticalAlignment" Value="Top"></Setter>
        </Style>
    </StackPanel.Resources>
    <Button Style="{StaticResource controlStyle}" Content="Button"   />
    <CheckBox Style="{StaticResource controlStyle}" Content="CheckBox" />
    <Rectangle Style="{StaticResource controlStyle}" Stroke="Black" />
</StackPanel>

Inherit

可以加入 BaseOn=”{..}” 來繼承其他的 style 設定:
<Style x:Key="controlStyleBold" BasedOn="{StaticResource controlStyle}">
    <Setter Property="Control.FontWeight" Value="Bold"/>
</Style>

Restricting the Styles

可以透過 TargetType=”{x:Type …}” 來限制 element 的類別:
<Style x:Key="controlStyle" TargetType="{x:Type Button}">
    <Setter Property="Control.FontSize" Value="15"/>
</Style>

Implicit Styles

上列的範例,若省略了前面的 x:Key,則會將所有 Button style 都變更了。
<Style TargetType="{x:Type Button}">
    <Setter Property="Control.FontSize" Value="15"/>
</Style>

Property Triggers

可以透過 Style.Triggers 判斷 property 的數值,變動另一項 property 的內容:
<Style x:Key="controlStyle" TargetType="{x:Type Button}">
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="FontSize" Value="15"/>
        </Trigger>
    </Style.Triggers>
    <Setter Property="FontSize" Value="12"/>
    <Setter Property="Height" Value="25"/>
</Style>

Data Triggers

也可以經由資料內容動態改變 property
<Style TargetType="{x:Type TextBox}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="1">
            <Setter Property="Background" Value="LightBlue"/>
        </DataTrigger>
    </Style.Triggers>
</Style>

Logical OR Triggers

無論是 property trigger 或是 data trigger,都可以使用多項設定,來達到 or 效果:
<Style TargetType="{x:Type TextBox}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="1">
            <Setter Property="Background" Value="LightBlue"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="2">
            <Setter Property="Background" Value="LightCoral"/>
        </DataTrigger>
    </Style.Triggers>
</Style>

Logical AND Triggers

如果想使用 and 產生 trigger,就必須使用 MultiTrigger MultiDataTrigger指令:
<Style.Triggers>
    <MultiTrigger>
        <MultiTrigger.Conditions>
            <Condition Property="IsMouseOver" Value="True"/>
            <Condition Property="IsEnabled" Value="True"/>
        </MultiTrigger.Conditions>
        <Setter Property="Background" Value="AliceBlue"/>
    </MultiTrigger>
</Style.Triggers>

2011年9月8日

WPF Application

WPF Application 會使用到的一些基本功能

Command-line argument

應用程式的參數可以透過 string[] args = System.Environment.GetCommandLineArgs(); 來取得

 

Persisting and Restoring Application State

過去 Windows 的應用程式多半使用 Registry 或執行目錄中的檔案來存取,
WPF 則建議採用 isolated storage,除了更容易使用外,
也與 Silverlight Application, XAML Browser Application 能有一致性。
isolated storage 以隱藏檔,存放在 current user's Document 的目錄中。

儲存:
IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForAssembly();
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("myFile", System.IO.FileMode.Create, file))
{
    using (StreamWriter writer = new StreamWriter(stream))
    {… }
}

讀取:
IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForAssembly();
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("myFile", System.IO.FileMode.OpenOrCreate, file))
{
    using (StreamReader reader = new StreamReader(stream))
    {… }
}

Splash Screen

Splash Screen 是指程式載入前,所顯示的形象圖案,
在 WPF 中的設定十分簡單,只要將圖檔加入專案中,
然後將 Build Action 屬性設定為 SplashScreen 即可。

 

Deployment

有 Windows Installer 與 ClickOnce 兩種方式可以選擇,
Windows Installer 是以專案的方式加入方案,與 ClickOnce 比較,有下列好處:

  1. 可以設計安裝介面與安裝流程
  2. 可以掌控要安裝的所有檔案
  3. 可以將分享的組件,放置在 Global Assembly Cache
  4. 可以註冊 COM 元件
  5. 可以提供 user 選項,決定安裝的內容
  6. 可以透過 DVD 離線安裝

ClickOnce 則是直接在執行專案的 Publish 屬性表中,設定資料,然後直接佈署,
其優點如下:

  1. 採連線方式安裝,有自動更新版本與退版的功能
  2. 可以在每次網頁關閉時即清除,或安裝在本機中
  3. 檔案存放在獨立的區域,不會影響到其他軟體與檔案
  4. Uninstall 可以完整清除所有檔案 (Full-trust application 則不一定)
  5. 與 .NET code access security 整合,程式可以在權限受限下執行

 

WPF Browser Applications

在新增專案時,除了 WPF Application 外,還有另一種 WPF Browser Application,
或稱為 XAML Browser Application (XBAP)
使用 ClickOnce 的技術,可以直接在 browser 的畫面上執行。
ClickOnce 型態的 Application 為了有較好的效能,會以 cache 方式處理,
更新軟體時,必須增加 version number 編號,否則仍會執行 cache 上舊版本。
WPF Browser Application 僅有 Internet Zone 等級的權限,因此有許多限制;
例如無法存取 local file, Registry, 或開啟新的 window,並在 Firefox 中,
無法使用 WebBrowser。

如果要將 Application 改為 full-trust 等級,可以在專案的 Security 屬性頁中修改,
或依照下列步驟更改:

  1. 在 app.manifest 的 PermissionSet 加入 Unrestricted="true"
    <PermissionSet class=… ID="Custom" SameSite="Site" Unrestricted="true">
  2. 更改專案檔案 .csproj (or .vbproj),將 <TargetZone>Internet</TargetZone>
    改為<TargetZone>Custom</TargetZone>

2011年9月7日

WPF Binary Resource


WPF Resource 的學習筆記。

Package

  1. Embedded in assembly,適合 toolbar icon, logo 等圖案
    使用方法,是將檔案加入專案,將 Build Action 設定為 Resource
  2. 分離的外部檔,但是登記在程式中
    使用方法,也是將檔案加入專案,將 Build Action 設定為 Content
    另外,記得將 Copy to Output Directory 設為 Copy Always 或 Copy if newer
    可以在編譯時,自動依照專案中的目錄,存放到編譯好的環境中
  3. 執行階段才能知道的 resource,例如由使用者指定載入的圖片

Localize

  1. 和語系相關的 localizable resource
  2. 和語系無關的 language-neutral resource

Accessing Binary Resource

可以透過 uniform resource identifier (URI) 來指定 resource 位置
利入 <Image Width="32" Height = "32" Source="logo.png"/>
下列是各種使用狀況:
  1. Source=" Images/logo.png"
    說明:圖案位於 Images 目錄下的 logo.png
  2. Source="c:\Users\John\Documents\logo.png" or
    Source=file://c:/Users/John/Documents/logo.png
    說明:直接指定檔案在本機的目錄位置
  3. Source="http://myWebsite/image/logo.png"
    說明:圖檔位於網站上
  4. Source=" /MyDll;component/Images/Money.png"
    說明:圖檔位於 MyDll assembly 下的 images (僅適用 embedded)
  5. Source=" pack://siteOfOrigin:,,,/Images/logo.jpg"
    說明:圖檔位於所在位置的 images 子目錄下

2011年8月8日

Using GDI+ with MFC

MFC 只提供 GDI 繪圖系統,想要有現代與流行的畫面,最快最簡單的方法,就是加入 GDI+。雖然在名稱上,只多了一個 + 號,但增加的功能卻不少。
這些功能有漸層筆刷 (Gradient Brushes)、透明混色 (Alpha Blending)、曲線 (Cardinal Splines & Bezier Splines)、平面座標轉換 (Transformations and the Matrix Object)、Scalable Regions 以及支援了更多的圖檔格式 (bmp, gif, jpeg, png, tiff, icon, exif, wmf, emf)。此外,還有 GraphicsPath 可以利用線、矩形、橢圓、曲線、文字..等元素,來產生向量資訊。
要在 MFC 上使用 GDI+ 的步驟如下:
  1. 至微軟 MSDN下載 Windows Platform SDK
  2. 建立 MFC 專案,並設定 include 與 lib 等相關連結
  3. 在 stdafx.h 加入下列程式碼
    #include <gdiplus.h>
    using namespace Gdiplus;
    #pragma comment(lib, "gdiplus.lib")
    
  4. 在繼承 CwinApp 的子物件中,加入下列變數的宣告
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    
  5. 在 InitInstance() 加入
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    
  6. 最後在 ExitInstance() 加入
    GdiplusShutdown(gdiplusToken);
    
完成後,便可以開始使用GDI+了,下列的程式碼,是使用 GDI+ 的範例:
void CTestGDIPlusView::OnDraw(CDC* pDC)
{
	CTestGDIPlusDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	Pen myPen(Color(255, 255, 0, 0), 3);
	Graphics myGraphics(pDC->m_hDC);

	myGraphics.DrawLine(&myPen, 20, 10, 200, 100);
}

最後,還有一個問題必須特別注意:MFC 在 debug 下,為了 memory leak 與錯誤行號的顯示,會在每個 cpp 檔案開頭,利用下列程式,將 new 宣告為 DEBUG_NEW。
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
因此 Graphics 物件若是以 new 產生,會發生 C2660 的錯誤訊息。解決辦法是將這段程式刪除,或直接修改 GdiplusBase 程式。方法請參考微軟的網站 PRB: Microsoft Foundation Classes DEBUG_NEW Does Not Work with GDI+
[Reference]
Using GDI+
Using GDI+ with MFC or native C/C++

2011年8月7日

Unicode in MFC CString

告別 MFC 大概有 10 年以上的時間,因為工作,得重新溫習這套平台。用慣了 C# 之後,回過來使用MFC,老覺得 "卡卡的",很不順手。
十年前,unicode 還不是很普及,在字串轉換上,常使用 (ch & 0x80) 來檢查是不是中文字;但是現在已經有很多函式可以支援,我利用下列簡單的程式碼,將字串的轉換記錄下來,免得下回忘了,又得重新詢一次。

 
int len = str.GetLength();
for (int i=0; i<len; i++)
{
	TCHAR ch = str[i];

	TCHAR buffer[255];
	_stprintf_s(buffer, 255, _T("Index:%d Char:%c"), i, ch);

	CString strf;
	strf.Format(_T("Index:%d Char:%c"), i, ch);
}
一些常用的字串函式,如 strcat, strcpy, strcmp,..等,都可以找到對應的 _tcscat, _tcscpy, _tcscmp, 透過 tchar.h 便可以不必擔心是不是在 unicode 的編譯環境下了。
[reference]
CString: http://www.flounder.com/cstring.htm

2011年5月11日

Add an uninstall shortcut to setup project

使用 VS2010 建立 WPF 的 Setup Project,若希望在程式集的錄下,有一個解除安裝的按鈕,可以採用下列的方法。

  1. 在 Setup Project 的 User's Programs Menu 點選右鍵,選擇 Add / Project Output / Primary Output,並將名稱改為 Unstall xxx
  2. 將此 Shortcut 的 Argument 屬性設為 /u=[ProductCode]
  3. 上列設定將產生 unstall 項目,並在點選後,呼叫原 AP 程式,並帶入 /u=[ProductCode] 參數。參數中的 ProductCode 會自動轉換成 AP 的 guid。接著將下列的程式碼加到 WPF 的 App.xaml.cs 的 OnStartup(),來實際執行 unstall 的工作。

 
protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    // 解除安裝
         string[] arguments = Environment.GetCommandLineArgs();
    foreach (string argument in arguments)
    {
        string[] items = argument.Split(new char[] { '=' });
        if (items.Length >= 2 && items[0].ToLower() == "/u")
        {
            string guid = items[1];
            string path = Environment.GetFolderPath(Environment.SpecialFolder.System);
            ProcessStartInfo info = new ProcessStartInfo(path + @"\msiexec", "/x" + guid); 
            Process.Start(info);
            this.Shutdown();
        }
    }
}
[Reference]
Add an uninstall start menu item to your .NET deployment project

2011年5月5日

WPF TileBrush

WPF 的 TileBrush 有 BrushMappingMode.Absolute 與 BrushMappingMode.RelativeToBoundingBox 兩種模式:

Absolute 採用固定的像數,Tile 圖案不會隨著物件的伸展而改變大小與形狀:



RelativeToBoundingBox 則相對於物件的長寬比,因此數量是不變的:



除了 TileBrush.ViewportUnits 設定的差異外,若 Tile 的圖案採用向量而不是圖檔時,要特別注意 Absolute 的用法。Absolute 以像數計算,DrawingBrush 會畫不出東西來,必須轉成 ImageBrush 才能正常運作,以下是這兩段程式的差異。

BrushMappingMode.RelativeToBoundingBox:
 
        public MainWindow()
        {
            InitializeComponent();

            Rectangle exampleRectangle = new Rectangle();

            GeometryDrawing backgroundSquare =
                new GeometryDrawing(Brushes.White, null, new RectangleGeometry(new Rect(0, 0, 100, 100)));

            GeometryGroup aGeometryGroup = new GeometryGroup();
            aGeometryGroup.Children.Add(new RectangleGeometry(new Rect(0, 0, 50, 50)));
            aGeometryGroup.Children.Add(new RectangleGeometry(new Rect(50, 50, 50, 50)));

            LinearGradientBrush checkerBrush = new LinearGradientBrush();
            checkerBrush.GradientStops.Add(new GradientStop(Colors.Black, 0.0));
            checkerBrush.GradientStops.Add(new GradientStop(Colors.Gray, 1.0));

            GeometryDrawing checkers = new GeometryDrawing(checkerBrush, null, aGeometryGroup);

            DrawingGroup checkersDrawingGroup = new DrawingGroup();
            checkersDrawingGroup.Children.Add(backgroundSquare);
            checkersDrawingGroup.Children.Add(checkers);

            DrawingBrush myBrush = new DrawingBrush();
            myBrush.Drawing = checkersDrawingGroup;
            myBrush.ViewboxUnits = BrushMappingMode.RelativeToBoundingBox;
            myBrush.Viewport = new Rect(0, 0, 0.25, 0.25);
            myBrush.TileMode = TileMode.Tile;
            exampleRectangle.Fill = myBrush;

            TilePanel.Children.Add(exampleRectangle);
        }



BrushMappingMode.Absolute:
 
        public MainWindow()
        {
            InitializeComponent();

            Rectangle exampleRectangle = new Rectangle();

            GeometryDrawing backgroundSquare =
                new GeometryDrawing(Brushes.White, null, new RectangleGeometry(new Rect(0, 0, 100, 100)));

            GeometryGroup aGeometryGroup = new GeometryGroup();
            aGeometryGroup.Children.Add(new RectangleGeometry(new Rect(0, 0, 50, 50)));
            aGeometryGroup.Children.Add(new RectangleGeometry(new Rect(50, 50, 50, 50)));

            LinearGradientBrush checkerBrush = new LinearGradientBrush();
            checkerBrush.GradientStops.Add(new GradientStop(Colors.Black, 0.0));
            checkerBrush.GradientStops.Add(new GradientStop(Colors.Gray, 1.0));

            GeometryDrawing checkers = new GeometryDrawing(checkerBrush, null, aGeometryGroup);

            DrawingGroup checkersDrawingGroup = new DrawingGroup();
            checkersDrawingGroup.Children.Add(backgroundSquare);
            checkersDrawingGroup.Children.Add(checkers);

            DrawingImage image = new DrawingImage(checkersDrawingGroup);
            ImageBrush myBrush = new ImageBrush(image);
            myBrush.Stretch = Stretch.None;
            myBrush.TileMode = TileMode.Tile;
            myBrush.Viewport = new Rect(0, 0, 100, 100);
            myBrush.ViewportUnits = BrushMappingMode.Absolute;
            exampleRectangle.Fill = myBrush;

            TilePanel.Children.Add(exampleRectangle);
        }

[Reference]
WPF Brushes Overview
ImageBrush for a BackGround grid in WPF

Deploying Vue & .NET with Google OAuth on GCP Cloud Run

Deploying Vue & .NET with Google OAuth on GCP Cloud Run Deploying Vue & .NET with Google OAuth on GCP Cloud Run...