Flyoutを別XAMLにしたときのメモ

Flyoutをストアアプリで使用する場合、多くは、使用するXAMLページに直接描くと思うのですが、これを別XAMLにしてアタッチすることもできます。

別XAMLとして作成する場合、とりあえず「追加>新しい項目>ポップアップの設定」で「SettingsFlyout」を継承したXAMLを作成して

「SettingsFlyout」を「Flyout」にCSもXAMLも書き換えます。

XAML上では「Flyout」で使用できない「IconSource」「Title」「d:DesignWidth」を削除します。

問題点として「Flyout」では「FindName」が使用できないため、「Binding」などに制限がかかります。
「DataContext」でデータバインドはできるので、これを利用してデータのやり取りをすることになります。

作成したXAMLは「FlyoutBase.AttachedFlyout」にアタッチしておきます。
外部とのやり取りは、「Flyout」にイベントを作成公開してやり取りします。

オープン、クローズはベースのイベントとして使用できるので、クローズ時にデータを初期化します。
名前を追うことはできないので、FrameworkElementを追いかけるか、下記のようにバインドしたプロパティデータをリセットして初期化します。

        private void Flyout_Closed(object sender, object e)
        {
            var con = this.Content;

            ((StackPanel)con).DataContext = new NewProp();
        }

手間がかかるので、使用するXAMLページにFlyoutを書き込んだほうが名前の仕様もできるので分離するのでなければそのほうがいいでしょう。

コードスニペット

プロパティの記載が一々同じことを書かないとで面倒だなーという部分を「コードスニペット」としてまとめると便利です。
通常の「prop」だと「public int MyProperty { get; set; }」だけで、これはこれで楽なのですが、制御部分もという場合は物足りません。

MSエヴァンジェリストの高橋忍氏作成された【Dependency Property for Windows Phone コードスニペット】これをベースに改造をして「添付プロパティ」としてコードを展開するようにしました。
ファイルはテキストとして「DependencyPropertyWP.snippet」で保存しますが、文字コードをutf-8で保存しましょう。
マイ ドキュメントの「Visual Studio 2012\Code Snippets\Visual C#\My Code Snippets」に保存してください。
VS内でpropwp2[tab]で使用できます。

Windowsストアアプリで使用する場合は「[System.ComponentModel.Description(“説明文”), System.ComponentModel.CategoryAttribute(“タブ”)]」はコメントアウトすればOKです。

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2008/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Dependency Property for Windows Phone</Title>
      <Shortcut>propwp2</Shortcut>
      <Description>Dependency Property for Windows Phone コード スニペット</Description>
      <Author>shinobu takahashi / CryEarth tomoyuki sasaki</Author>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>type</ID>
          <ToolTip>プロパティの型</ToolTip>
          <Default>int</Default>
        </Literal>
        <Literal>
          <ID>name</ID>
          <ToolTip>プロパティ名</ToolTip>
          <Default>MyPropName</Default>
        </Literal>
        <Literal>
          <ID>ownerclass</ID>
          <ToolTip>このプロパティを定義しているここのクラスです</ToolTip>
          <Default>ownerclass</Default>
        </Literal>
      </Declarations>
      <Code Language="CSharp">
        <![CDATA[[System.ComponentModel.Description("説明文"), System.ComponentModel.CategoryAttribute("タブ")]
	 public $type$ $name$
        {
            get { return ($type$)GetValue($name$Property); }
            set { SetValue($name$Property, value); }
        }

        public static readonly DependencyProperty $name$Property =
            DependencyProperty.Register(
				"$name$",
				typeof($type$),
				typeof($ownerclass$),
				new PropertyMetadata(   // メタデータ
					"",		// デフォルト値
					new PropertyChangedCallback(On$name$Changed)) );

	private static void On$name$Changed(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            $ownerclass$ userControl = obj as $ownerclass$;

            if (userControl != null)
            {
                $type$ newValue = ($type$)e.NewValue;
            }
        }

$end$]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

同じソリューションの別プロジェクトにあるファイルの読み込み

まあ、単純な事なんですが「ms-appx:///プロジェクトの名前/ファイルの名前」これだけです。
フォルダがあるならフォルダ名入れればOKです。

データを別なプロジェクトに逃がしておきたい、データとコードは分離したい等の時はプロジェクトの追加で、Windowsランタイムプロジェクトとして作成して参照しておく。

判り切ってることのネタですが、最近記事書いてなかったのでとりあえず書いてみました。

メモリ容量を意識しよう

WindowsストアアプリやWindowsPhoneアプリはガベージコレクション(GC)により、メモリ管理は比較的自動化され、あまり気にしなくてもよいようになっていますが、個人的にはGCにべったりではなく自分でメモリ容量を認識しながらアプリを作るべきではないかと思います。
GC管理されているからとメモリの使用量を気にかけずに開発すれば、それはメモリ不足などを引き起こし、アプリの停止や強制終了を起こします。

こういった現象を起こさないためにも、自身のコードやデータがどれだけのメモリを使用し、さらに使いそうなのか、どこでメモリの強制解放をすることでほかの部分に影響を与えずにメモリを適切に使うことができるかを見ていくべきでしょう。

必要ないデータは解放する、画像データやDBデータを必要な容量だけ使用するようにして、適時入れ替えるようにするなど単純なことですがこれらを意識してデータの最適化や、コードの最適化を図っていくようにしましょう。

何でこんなこと書いたかといえば、自分が作成したアプリでやらかしていたからです。
現象は解消して提出したので、問題なく公開されましたが、気が付いていなかったらリジェクトされた可能性または使用された方から指摘されたかなと思います。

たとえば、カメラを使用したアプリや画像を一覧表示するようなアプリの場合、保存用の大きな画像と一覧に表示する小さな画像をそれぞれ用意、生成しておき使い分けることでメモリの抑制になると思われます。

アプリ作成の際気にかけてみてはいかがでしょうか?

StreamSoketのSampleコード

非同期化していないところがあったりするのであまりいいサンプルではないですが、StreamSoketでの通信コードです。
WriteAsync、LoadAsyncで書き込み/読み込みを行うようにしてあります。
DataReader、DataWriterを使用した際にうまくいかなかったので、このような形で実装しています。

///////////////////////////////////////////////////////////////////////////////////////////////
// サーバへ接続等

        #region サーバへ接続
        /// <summary>
        /// TCPでサーバへ接続
        /// </summary>
        /// <param name="hostname"></param>
        /// <param name="serverport"></param>
        public async void TcpSocketConnect(string hostname, string serverport)
        {
            SocketStatusEventArgs socketevent = new SocketStatusEventArgs();

            // 接続済み
            if (SocketDatacontext.Connected)
            {
                socketevent.SocketStatus = StringResourceLoader.GetString("Already");
                OnSocketStatus(socketevent);
                cryearthlib.DebugLoging.DebugPrint(socketevent.SocketStatus);
                await Task.Run(() => { throw new PopClassException(SocketDatacontext.SocketMessage); });
                return;
            }

            if (SocketDatacontext.TcpSocket == null)
            {
                SocketDatacontext.TcpSocket = new Windows.Networking.Sockets.StreamSocket();

                bool currentSetting = SocketDatacontext.TcpSocket.Control.NoDelay;
                SocketDatacontext.TcpSocket.Control.KeepAlive = true;
                SocketDatacontext.TcpSocket.Control.NoDelay = false;
            }

            try
            {
                /// タイムアウト用
                CancellationTokenSource cts = new CancellationTokenSource();

                socketevent.SocketStatus = StringResourceLoader.GetString("Trying");

                OnSocketStatus(socketevent);

                cryearthlib.DebugLoging.DebugPrint(socketevent.SocketStatus); 
                
                var serverHost = new HostName(hostname);

                // タイムアウトの設定
                cts.CancelAfter(10000);

                await SocketDatacontext.TcpSocket.ConnectAsync(serverHost, serverport).AsTask(cts.Token);

                // DataReaderの作成
                SocketDatacontext.PeerReader = new DataReader(SocketDatacontext.TcpSocket.InputStream);
                SocketDatacontext.PeerReader.InputStreamOptions = InputStreamOptions.Partial;

                SocketDatacontext.Connected = true;
                SocketDatacontext.Closing = false;
                socketevent.SocketStatus = StringResourceLoader.GetString("Connection");
                OnSocketStatus(socketevent);
                cryearthlib.DebugLoging.DebugPrint(socketevent.SocketStatus);
            }
            catch (Exception ex)
            {
                socketevent.SocketStatus = StringResourceLoader.GetString("Connecterror") + ex.Message;
                OnSocketStatus(socketevent);

                SocketDatacontext.Closing = true;
                SocketDatacontext.TcpSocket.Dispose();
                SocketDatacontext.TcpSocket = null;
                SocketDatacontext.Connected = false;

                OnSocketStatus(socketevent);
                cryearthlib.DebugLoging.DebugPrint(socketevent.SocketStatus);
                throw new PopClassException(SocketDatacontext.SocketMessage);
            }
        }
        #endregion

///////////////////////////////////////////////////////////////////////////////////////////////
// サーバへデータを送る

        #region サーバへデータを送る
        public async void ResultSend(string req)
        {
            SocketStatusEventArgs socketevent = new SocketStatusEventArgs();

            if (!SocketDatacontext.Connected)
            {
                socketevent.SocketStatus = StringResourceLoader.GetString("Mustbeconnectedtosend");
                OnSocketStatus(socketevent);

                cryearthlib.DebugLoging.DebugPrint(socketevent.SocketStatus);
                return;
            }

            try
            {
                // サーバへリクエスト送信
                if (req != "")
                {
                    StreamSocketLib writeSokect = new StreamSocketLib();

                    socketevent.SocketStatus = StringResourceLoader.GetString("Tryingtosenddata");
                    OnSocketStatus(socketevent);

                    writeSokect.DataSend(SocketDatacontext.TcpSocket, req);

                    cryearthlib.DebugLoging.DebugPrint("Data Writer End");
                }
            }
            catch (Exception exception)
            {
                socketevent.SocketStatus = "Send data or receive failed with error: " + exception.Message;
                OnSocketStatus(socketevent);
            }
        }
        #endregion
        /// <summary>
        /// データ書き込みフロント
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="senddata"></param>
        public void DataSend(StreamSocket socket, string req)
        {
            _socket = socket;

            Encoding encoding = Encoding.UTF8;

            byte[] bydata = encoding.GetBytes(req + Environment.NewLine);

            if (socket == null)
            {
                cryearthlib.DebugLoging.DebugPrint("socket Error : socket null");
                throw new Exception("socket Error : socket null");
            }

            try
            {
                var task = SocketDataWrite(bydata);
                if (!task.IsCompleted)
                {
                    var ret = task.Wait(timeout);
                    if (!ret)
                    {
                        cryearthlib.DebugLoging.DebugPrint("Write Time Out");
                    }
                }

                if (!task.Result)
                {
                    cryearthlib.DebugLoging.DebugPrint("Write Time Out");
                }
            }
            catch (Exception ex)
            {
                cryearthlib.DebugLoging.DebugPrint("DataWrite Error : " + ex.Message);
                throw new Exception("DataWrite Error : " + ex.Message);
            }
        }

        /// <summary>
        /// データ書き込み本体
        /// </summary>
        /// <param name="senddata"></param>
        /// <returns></returns>
        public async Task<bool> SocketDataWrite(byte[] senddata)
        {
            var writeTask = _socket.OutputStream.WriteAsync(senddata.AsBuffer()).AsTask();

            if (!writeTask.IsCompleted)
            {
                var writeret = writeTask.Wait(timeout);
                if (!writeret)
                {
                    return false;
                }
            }

            var flushTask = _socket.OutputStream.FlushAsync().AsTask();

            if (!flushTask.IsCompleted)
            {
                var flushret = flushTask.Wait(timeout);
                if (!flushret)
                {
                    return false;
                }
            }
            return true;
        }



///////////////////////////////////////////////////////////////////////////////////////////////
// サーバからのデータを読み込む

        #region サーバからのデータを読み込む
        public async Task<string> ResultRead()
        {
            SocketStatusEventArgs socketevent = new SocketStatusEventArgs();

            if (!SocketDatacontext.Connected)
            {
                socketevent.SocketStatus = StringResourceLoader.GetString("Mustbeconnectedtosend");
                OnSocketStatus(socketevent);

                return null;
            }

            try
            {
                var lastChar = string.Empty;
                var fullString = string.Empty;

                cryearthlib.Sleep(200);
                
                socketevent.SocketStatus = StringResourceLoader.GetString("Tryingtosenddata");
                OnSocketStatus(socketevent);

                CancellationTokenSource cts = new CancellationTokenSource(1000);
                cts.Token.ThrowIfCancellationRequested();
                var bytesRead = SocketDatacontext.PeerReader.LoadAsync(1024);

                Debug.WriteLine("StreamWriteAndRead : reader.LoadAsync : " + bytesRead.Status.ToString());

                if (bytesRead.Status != Windows.Foundation.AsyncStatus.Completed)
                {
                    cts.Cancel();

                    Debug.WriteLine("StreamWriteAndRead : cts.Cancel");
                    Debug.WriteLine("StreamWriteAndRead : " + bytesRead.Status.ToString());
                }
                else
                {
                    Debug.WriteLine("StreamWriteAndRead : ReadString : " + bytesRead.Status.ToString());

                    while (SocketDatacontext.PeerReader.UnconsumedBufferLength > 0)
                    {
                        fullString += SocketDatacontext.PeerReader.ReadString(SocketDatacontext.PeerReader.UnconsumedBufferLength);
                    }
                }

                SocketDatacontext.SocketMessage = socketevent.SocketStatus = fullString;
                OnSocketStatus(socketevent);

                cryearthlib.DebugLoging.DebugPrint(socketevent.SocketStatus);
                cryearthlib.DebugLoging.DebugPrint("reader.End");
                return fullString;
            }
            catch (Exception exception)
            {
                socketevent.SocketStatus = "Receive failed with error: " + exception.Message;
                OnSocketStatus(socketevent);
                cryearthlib.DebugLoging.DebugPrint(socketevent.SocketStatus);
                return socketevent.SocketStatus;
            }
        }
#endregion

GridのRow、Columnに合わせてドラッグ移動

とりあえずドラッグの方向に合わせて移動
これだけだと過敏すぎるので移動量か移動先のRow、Column数値をとって行うとかしないとだめだと思う。

GridViewItemでDragItemThemeAnimationなんかの組み合わせのほうがいいような気もする

またはこれで行ってる「SetValue(Grid.ColumnProperty,~)」を利用してオセロや囲碁などのほうが現実的かもしれない。

        private void MainChara_ManipulationDelta(object sender, Windows.UI.Xaml.Input.ManipulationDeltaRoutedEventArgs e)
        {
        	// TODO: ここにイベント ハンドラーのコードを追加します。
            // MainChara.SetValue(Grid.RowProperty, 3);
            var radian = Math.Atan2(e.Delta.Translation.Y, e.Delta.Translation.X);
            double degree = radian * 180.0 / 3.141592653589793;

            if (degree < 0)
            {
                degree += 360;
            }

            if (degree > 315 && degree < 380)
            { 
                // 右
                var value = (int)MainChara.GetValue(Grid.ColumnProperty);
                if (value != 6)
                {
                    value++;
                    MainChara.SetValue(Grid.ColumnProperty, value);
                }
            }
            else if (degree > 0 && degree < 45)
            {
                // 右
                var value = (int)MainChara.GetValue(Grid.ColumnProperty);
                if (value != 6)
                {
                    value++;
                    MainChara.SetValue(Grid.ColumnProperty, value++);
                }
            }
            else if (degree > 45 && degree < 135)
            { 
                // 下
                var value = (int)MainChara.GetValue(Grid.RowProperty);
                if (value != 6)
                {
                    value++;
                    MainChara.SetValue(Grid.RowProperty, value);
                }
            }
            else if (degree > 135 && degree < 225)
            {
                // 左
                var value = (int)MainChara.GetValue(Grid.ColumnProperty);
                if (value != 0)
                {
                    value--;
                    MainChara.SetValue(Grid.ColumnProperty, value);
                }
            }
            else
            {
                // 上
                var value = (int)MainChara.GetValue(Grid.RowProperty);
                if (value != 0)
                {
                    value--;
                    MainChara.SetValue(Grid.RowProperty, value);
                }
            }
        }

Windowsストアアプリでの物理キーボードの有無

物理キーボードの有無を確認することで、「キーボード操作」とタブレットでの「コントローラーUI操作」の表示切替を実装することができる。

「KeyboardCapabilities Class」を使用して確認
「KeyboardPresent」が読み取り専用であるので、これを確認すること。

下記のページで確認したもの
http://msdn.microsoft.com/ja-jp/library/windows/apps/windows.devices.input.keyboardcapabilities.aspx

Windowsストアアプリで使用できるグラフコントロール

Modern UI (Metro) Charts for Windows 8, WPF, Silverlight
License:Microsoft Public License (Ms-PL)

・これから調べる

WinRT XAML Toolkit
License:The MIT License (MIT)
上記のチャートは「Windows 8 Toolkit – Charts and More(License:Common Development and Distribution License (CDDL))」からのポート
・これから調べる
・WinRT XAML ToolkitはどうやらWin8.1ベースでVS2013になっている模様

どちらもベータで、「Modern UI (Metro) Charts for Windows 8, WPF, Silverlight」には折れ線グラフはまだない

有料であれば、下記のようなものもあります。
NetAdvantage for Windows UI

デスクトップでWindowsRuntimeAPIを使う場合の忘備録

MSエヴァンジェリストのVS魂100連発から
http://www.youtube.com/watch?v=xmwTbfWKDyA

・最初の設定
デスクトップアプリのプロジェクトをいったん「プロジェクトのアンロード」を行う
アンロードしたプロジェクトを右クリックして「.csprojの編集」をクリック
「PropertyGroup」内に「TargetPlatformVersion」を追加して「8.0」とする
上書き保存したらプロジェクトを再読み込みする
「参照設定」から「参照の追加」をクリックすると左側に「Windows」が増えているのでコアから「Windows」を追加
「ブラウズ」から「参照」で「\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5」から「System.Runtime.dll」を追加する。
「using Windows.Foundation;」を追加する。
「System.Runtime.dll」は.NETのCollectionとRTのCollectionに互換性がないため、その互換変換のために必要になる。

・async/awaitの非同期に対応する
「参照設定」から「参照の追加」をクリック
「ブラウズ」から「参照」で「\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5」から「System.Runtime.WindowsRuntime.dll」「System.Threading.Tasks.dll」の二つを追加
これを追加することで「async/await」が使用可能になる。
使わない場合「IAsyncOperation」をもとに書くようにする。

今更ながらの忘備録、これでストアアプリ管理ツールの作成などにもWindowsRuntimeAPIを使える。

indexを指定可能なIntデータCollectionProperty

collectionなPropertyにIndexを指定して通知したい場合に下記のように行う
通常ではIndex指定でやり取りはできないと思うのでメモがてら

    /// <summary>
    /// IntデータcollectionProperty
    /// </summary>
    public class CollectionDataProperty : CryEarth.lib.cryearthlib.NotifyPropertyChangedMethod
    { 
        private readonly Collection<int> items = new Collection<int>();

        public CollectionDataProperty()
        {
            items.Clear();
        }

        public int this[int index]
        {
            get
            {
                // indexの境界チェック
                if (items.Count < index)
                {
                    return 0;
                }
                return items[index];
            }
            set
            {
                //indexの境界チェック
                if (items.Count < index+1)
                {
                    items.Add(value);
                }
                else if (items[index] == value)
                {
                    return;
                }
                else
                {
                    items[index] = value;
                }
                OnPropertyChanged(ITEMS);
            }
        }

        private const string ITEMS = "Item[]";
    }