XAML in Xamarin.Forms 基礎篇 電子書

XAML in Xamarin.Forms 基礎篇 電子書
XAML in Xamarin.Forms 基礎篇 電子書

Xamarin.Forms 快速入門 電子書

Xamarin.Forms 快速入門 電子書
Xamarin.Forms 快速入門 電子書

2014/07/09

在 Windows 8.1 中,如何將 BitmapImage 的圖片內容透過 WriteableBitmap 寫入到磁碟檔案上

WriteableBitmap 是個很好用的程式庫,因為我們可以透過 WriteableBitmap 任意繪製與產生您所想要的圖片與內容,不過,要如何將 WriteableBitmap 的圖片內容,轉換成為圖片檔案呢(jpg, jpeg, png)?

在底下的程式碼,我們從 App Package 套件中的 Assets 目錄下,讀入一個圖片檔案,並且將這個圖片檔案設定到 BitmapImage 物件中,這是為了要知道這個圖片的長度與寬度。

接著,我們產生一個 WriteableBitmap 物件,在建構式中,我們傳入的 WriteableBitmap 所需要的寬度與高度,接著,將剛剛讀入的圖片檔案,使用 SetSourceAsync 設定到 WriteableBitmap 物件上,接著,我們就可以透過 SaveToFile 這個方法,把這個 WriteableBitmap 寫入到圖片檔案內。

            StorageFile storageFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Images/Pages/PageBackground.jpg"));
            IRandomAccessStream stream = await storageFile.OpenAsync(FileAccessMode.Read);
            BitmapImage bitmapImage = new BitmapImage();
            await bitmapImage.SetSourceAsync(stream);
            stream.Seek(0);

            var wb1 = new WriteableBitmap(bitmapImage.PixelWidth, bitmapImage.PixelHeight);
            await wb1.SetSourceAsync(stream);
            StorageFile sf1 = await ApplicationData.Current.LocalFolder.CreateFileAsync("your.png");
            await SaveToFile(wb1, sf1, BitmapEncoder.PngEncoderId);



        public async Task SaveToFile(WriteableBitmap writeableBitmap, IStorageFile outputFile, Guid encoderId)
        {
            try
            {
                Stream stream = writeableBitmap.PixelBuffer.AsStream();
                byte[] pixels = new byte[(uint)stream.Length];
                await stream.ReadAsync(pixels, 0, pixels.Length);

                using (var writeStream = await outputFile.OpenAsync(FileAccessMode.ReadWrite))
                {
                    var encoder = await BitmapEncoder.CreateAsync(encoderId, writeStream);
                    encoder.SetPixelData(
                        BitmapPixelFormat.Bgra8,
                        BitmapAlphaMode.Premultiplied,
                        (uint)writeableBitmap.PixelWidth,
                        (uint)writeableBitmap.PixelHeight,
                        96,
                        96,
                        pixels);
                    await encoder.FlushAsync();

                    using (var outputStream = writeStream.GetOutputStreamAt(0))
                    {
                        await outputStream.FlushAsync();
                    }
                }
            }
            catch (Exception ex)
            {
                string s = ex.ToString();
            }
        }




2014/07/02

安裝 WinRT App 發生問題之除錯方法

經常有些時候,我們在安裝(不論透過市集或者使用 Sideloading 側載方式)App,有些時候會發生錯誤或者異常,我們可以透過 [事件檢視器] 來查看這些Windows Store App 再進行佈署、安裝、註冊的時候,發生了甚麼問題。

請使用滑鼠右擊螢幕左下方的視窗圖示,此時會彈出下圖對話窗,請選擇 [事件檢視器]


 我們需要觀察 事件檢視器 中的這三個節點上的資料
Application And Services Logs > Microsoft > Windows > AppXDeployment
Application And Services Logs > Microsoft > Windows > AppXDeployment-Server
Application And Services Logs > Microsoft > Windows > AppXPackagingOM


 在下圖中,我們展示了查看某個錯誤訊息,這是屬於 AppXDeployment-Server
我們看到這個訊息顯示了:
錯誤 0x80073CF9: AppX 部署操作失敗。此失敗的特定錯誤文字是: 另一個使用者已安裝此應用程式的未封裝版本。目前的使用者無法使用已封裝的版本來取代此版本。衝突的套件是 CLACOMUSICCORPORATION.MusicPlayAlong,它是由 CN=327F2EE7-B787-4EC2-8B56-6B65F0CBB4FB 發行。

記錄檔名稱:         Microsoft-Windows-AppXDeploymentServer/Operational
來源:            Microsoft-Windows-AppXDeployment-Server
日期:            2014/6/21 下午 08:19:46
事件識別碼:         404
工作類別:          (3)
層級:            錯誤
關鍵字:           AppXDeploymentServer 關鍵字
使用者:           SYSTEM
電腦:            VulcanHome81
描述:
錯誤 0x80073CF9: AppX 部署操作失敗。此失敗的特定錯誤文字是: 另一個使用者已安裝此應用程式的未封裝版本。目前的使用者無法使用已封裝的版本來取代此版本。衝突的套件是 CORPORATION.MPA,它是由 CN=32E7-B787-4EC2-8B56-6B65F4FB 發行。


事件 Xml:
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
  <System>
    <Provider Name="Microsoft-Windows-AppXDeployment-Server" Guid="{3F471139-ACB7-4A01-B7A7-FF5DA4BA2D43}" />
    <EventID>404</EventID>
    <Version>0</Version>
    <Level>2</Level>
    <Task>3</Task>
    <Opcode>0</Opcode>
    <Keywords>0x4000000000000001</Keywords>
    <TimeCreated SystemTime="2014-06-21T12:19:46.781919400Z" />
    <EventRecordID>63324</EventRecordID>
    <Correlation ActivityID="{2363FD72-8D4A-0001-02FE-63234A8DCF01}" />
    <Execution ProcessID="4572" ThreadID="4944" />
    <Channel>Microsoft-Windows-AppXDeploymentServer/Operational</Channel>
    <Computer>VulcanHome81</Computer>
    <Security UserID="S-1-5-18" />
  </System>
  <EventData>
    <Data Name="SummaryError">另一個使用者已安裝此應用程式的未封裝版本。目前的使用者無法使用已封裝的版本來取代此版本。衝突的套件是 CORPORATION.MPA,它是由 CN=32E7-B787-4EC2-8B56-6B65F4FB 發行。</Data>
    <Data Name="ErrorCode">0x80073cf9</Data>
  </EventData>
</Event>











SuspensionManager & NavigationHelper 之 App Lifecycle生命週期會使用到的相關事件與呼叫順序

想要充分發揮 WinRT & Windows Phone Universal App 效能,您一定要知道應用程式生命週期的相關知識,這裡,做個總結。當從App開始啟動後、到切換到不同的頁面、進入到 Suspend 模式下、重新回到App執行模式下等相關事件的執行順序。

當App在不同執行模式下運作,或者切換頁面的時候,您需要明確的掌握到那些事件會被執行,而且這些事件被呼叫的順序,如此,您方能夠有效的控制App的整體狀態與運作行為。

當App第一次啟動的時候,相關的事件呼叫順序
  • App.OnLaunched 事件(也就是 PreviousExecutionState 為 Nothing)
  • 第一個頁面.OnNavigatedTo 事件
  • 第一個頁面.LoadState 事件

當App進入到 Suspended 模式下,相關事件的呼叫順序
  • App.OnSuspending 事件
  • 第一個頁面.OnNavigatedFrom 事件
  • 第一個頁面.SaveState 事件

使用者把App從 Suspended 模式下,切換為正在顯示的相關事件呼叫順序
  • App.OnResuming 事件

使用者或者作業系統把 App 終止(例如使用者使用工作管理員強制關閉該App),相關事件呼叫順序
  • App.OnSuspending 事件
  • 第一個頁面.OnNavigatedFrom 事件
  • 第一個頁面.SaveState 事件


使用者從第一個頁面切換到第二個頁面的相關事件呼叫順序
  • 第一個頁面.OnNavigatingFrom事件
  • 第一個頁面.SaveState 事件
  • 第一個頁面.OnNavigatedFrom 事件
  • 第二個頁面.LoadState 事件
  • 第二個頁面.OnNavigatedTo 事件

使用者從第二個頁面回到第一個頁面的相關事件呼叫
  • 第二個頁面.OnNavigatingFrom事件
  • 第二個頁面.SaveState 事件
  • 第二個頁面.OnNavigatedFrom 事件
  • 第一個頁面.LoadState 事件
  • 第一個頁面.OnNavigatedTo 事件

使用者再次切換到第二個頁面的相關事件呼叫
  • 第一個頁面.OnNavigatedFrom 事件
  • 第一個頁面.OnNavigatingFrom事件
  • 第一個頁面.SaveState 事件
  • 第一個頁面.OnNavigatedFrom事件
  • 第二個頁面.LoadState 事件
  • 第二個頁面.OnNavigatedTo 事件

App進入到 Suspended 模式下的相關事件呼叫(例如:切換到桌面環境中,或者其他App)
  • App.OnSuspending 事件
  • 第二個頁面.SaveState 事件
  • 第二個頁面.OnNavigatedFrom 事件

App從 Suspended模式下,回到正常運作模式下的相關事件呼叫
  • App.OnResuming


App進入到 Suspended 模式下並且被作業系統強制終止
  • App.OnSuspending 事件
  • 第二個頁面.SaveState 事件
  • 第二個頁面.OnNavigatedFrom 事件

Microsoft DirectX Graphics Infrastructure (DXGI) 角色

DXGI 是一組用來設定和管理低階圖形與圖形卡資源的 API。如果沒有它,您就無法將遊戲的圖形繪製到視窗中!

您可以用這種方式思考 DXGI:為了直接存取 GPU 並管理其資源,必須有一個對應用程式描述它的方式。您所需最重要的 GPU 資訊就是繪製像素的位置,這樣它才能夠將這些像素傳送到螢幕上。這通常稱為「背景緩衝區」—GPU 記憶體中的一個位置,您可以在該處繪製像素,然後「翻轉」或「交換」,並在收到重新整理訊號時傳送到螢幕上。DXGI 可讓您取得該位置以及使用該緩衝區 (稱為「交換鏈結」,因為這是可交換的緩衝區鏈結,允許多個緩衝處理策略) 的方法。

若要這麼做,您需要有可寫入交換鏈結的存取權,以及將顯示交換鏈結之目前背景緩衝區的視窗控制代碼。您還需要將兩者連接,以確保作業系統會在您要求以背景緩衝區的內容重新整理視窗時,執行該動作。

開發 universal app ,如何在程式碼中,標示與切換 Windows store 或者 Windows phone 專用的程式碼

當我們在開發 universal app,讓 App 可以同時在兩個平台上執行,但是,某些情況,我們需要在特定平台上寫入只有該平台才擁有的功能,這個時候,您可以使用 [條件式編譯的符號] 來區隔,Windows Phone 使用 WINDOWS_PHONE_APP,而Windows Store 則使用 WINDOWS_APP。

那麼,我們要如何在 Visual Studio 上換不同平台,看到這些程式碼是否有效呢?
我們使用下圖做說明, NavigationHelper.cs 是共用程式碼區域的C#,在下圖內,我們看到了這行程式碼,只有 Windows Phone 才會使用的,如底下這行程式碼:

Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;

而底下的程式碼,是在 Windows Store App 環境下才會執行的。

                // 只有佔用整個視窗時才適用鍵盤和滑鼠巡覽
                if (this.Page.ActualHeight == Window.Current.Bounds.Height &&
                    this.Page.ActualWidth == Window.Current.Bounds.Width)
                {
                    // 直接接聽視窗,所以不需要焦點
                    Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated +=
                        CoreDispatcher_AcceleratorKeyActivated;
                    Window.Current.CoreWindow.PointerPressed +=
                        this.CoreWindow_PointerPressed;
                }

我們可以使用下圖編號1標示的地方,切換使用不同平台的 [條件式編譯的符號] ,如此,我們就可以看到不同平台的程式碼,是否有問題發生。

在下圖中,我們使用的是 Windows Store App平台的程式碼,您會看到了 Windows Phone 專屬的程式碼變成不可用的灰色了。


在下圖中,我們使用的是 Windows Phone App平台的程式碼,您會看到 Windows Phone 會用到的程式碼變成可用的黑色了。



2014/07/01

在WinRT Windows Phone App 中,想要下載較大的圖片、音樂、二進位檔案的方法

平常我們在 WinRT 或者 Windows Phone App中,想要取得網路上的檔案,可以透過 HttpClient 物件來進行取得這些檔案,不過,當這些網路上的檔案過大的時候,HttpClient 可能無法處理,這個時候我們就可以透過 BackgroundDownloader 物件來幫助我們做到背景下載這些檔案,卻又不會影響到 UI thread 執行緒,也就是不會造成您的 UI 凍結的問題。


            BackgroundDownloader downloader = new BackgroundDownloader();
             StorageFile destinationFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("vulcan.mp3");
            DownloadOperation download = downloader.CreateDownload(new Uri("http://host/path/vulcan.mp3), destinationFile);
            await download.StartAsync();
            ResponseInformation response = download.GetResponseInformation();

在 WinRT & Windows Phone 中,如何將顏色代碼文字,轉換成為 Windows.UI.Color 物件

底下的範例程式碼,提供了如何將顏色的代碼, #FF012345 或者 #012345 這樣的代碼,轉換成為 Windows.UI.Color 的物件。

        public static Color Parse(string color)
        {
            var offset = color.StartsWith("#") ? 1 : 0;

            string tt = color.Substring(offset);

            if (tt.Length > 6)
            {
                var a = Byte.Parse(color.Substring(0 + offset, 2), NumberStyles.HexNumber);
                var r = Byte.Parse(color.Substring(2 + offset, 2), NumberStyles.HexNumber);
                var g = Byte.Parse(color.Substring(4 + offset, 2), NumberStyles.HexNumber);
                var b = Byte.Parse(color.Substring(6 + offset, 2), NumberStyles.HexNumber);

                return Color.FromArgb(a, r, g, b);
            }
            else
            {
                var a = Byte.Parse("FF", NumberStyles.HexNumber);
                var r = Byte.Parse(color.Substring(0 + offset, 2), NumberStyles.HexNumber);
                var g = Byte.Parse(color.Substring(2 + offset, 2), NumberStyles.HexNumber);
                var b = Byte.Parse(color.Substring(4 + offset, 2), NumberStyles.HexNumber);

                return Color.FromArgb(a, r, g, b);
            }
        }



在 WinRT & Windows Phone 中,將非同步呼叫,轉換成為同步呼叫

有些時候,也許您不想要採用非同步方式的呼叫,不過,您所擁有的程式庫 Library ,卻都只有提供非同步方式呼叫的方法,這個時候,您可以採用底下的方式來將非同步的呼叫,轉換成為同步方式的呼叫。

                Task.Factory.StartNew(async () =>
                {
                    await IsolatedStorageJSON< List < AbnormalException > >.SaveToFileAsync("", "AbnormalException", listAbnormalException);
                });


另外,可以採用底下的方式(底下的範例是因為呼叫了 GetFileAsync 會傳回 IAsyncOperation<StorageFile> ,因此,我們可以透過這樣的方式,綁定了 Completed 事件,來判斷或者取回此次非同步呼叫的結果):

        public static void WinRTAsyncIntro()
        {
            IAsyncOperation< StorageFile > asyncOp = KnownFolders.PicturesLibrary.GetFileAsync("vulcan.png");
            asyncOp.Completed = OpCompleted;
        }
        private static void OpCompleted(IAsyncOperation< StorageFile > asyncOp, AsyncStatus status)
        {
                try
                {
                    StorageFile file = asyncOp.GetResults(); 
                }
                catch (Exception ex)
                {
                }

            asyncOp.Close();
        }

這是 GetFileAsync 的語法
public IAsyncOperation<StorageFile> GetFileAsync(
  string name
)

這裡還有另外一個方法,是比較有效率的,尤其是當您在 WinRT 系統下,需要用到 deferral 情境下,可以使用底下的方法,不過,需要特別注意的,這個方法,會造成當時的執行緒被鎖定,若您是在 UI Thread 下呼叫底下方法,會造成您的UI被凍結。

StorageFile myFile = KnownFolders.PicturesLibrary.GetFileAsync("vulcan.png").AsTask().GetAwaiter().GetResult();

2014/06/30

將圖片檔案直接設定到 BitmapImage

若想再增加 XAML 對於圖片檔案顯示的速度,您可以使用 BitmapImage 物件,將圖片檔案讀入到 BitmapImage 物件內,接著,您就可以將這個 BitmapImage 物件 設定到 XAML Image Element的 Source 屬性上,如此,就可以直接顯示出該圖片。

                StorageFile storageFile = await ApplicationData.Current.LocalFolder.GetFolderAsync(filename);
                IRandomAccessStream stream = await storageFile.OpenAsync(FileAccessMode.Read);
                BitmapImage bitmapImage = new BitmapImage();
                await bitmapImage.SetSourceAsync(stream);

                 this.XAMLImageElement.Source = bitmapImage ;



使用 HttpClient 下載圖片檔案到本機的 Isolated Storage

若您在 WinRT 系統下,有些時候您會想要自己將網路上的圖片檔案,下載到本機  Isolated Storage 檔案系統內,這個圖片檔案可以做為快取之用,下次當要顯示該圖片的時候,就可以直接從本機  Isolated Storage 目錄下,直接讀取這個圖片檔案出來顯示。

            var httpClient = new HttpClient();

            var randomAccessStream = new InMemoryRandomAccessStream();

            var contentUri = "http://host/path/path/name";
            var filename = "your file name";

            using (var responseStream = await httpClient.GetStreamAsync(new Uri(contentUri)))

                 StorageFolder folder = await ApplicationData.Current.LocalFolder;
 
                using (var fileStream = await folder.OpenStreamForWriteAsync(filename , CreationCollisionOption.ReplaceExisting))
                {
                    await responseStream.CopyToAsync(fileStream);
                    responseStream.Dispose();
                }