Tech Summit の資料・動画が公開されました
shunsukekawai.hatenablog.com
↑こちらで登壇した旨を記載しましたが、資料、動画が公開されました。
【Xamarin 開発の真実】 Microsoft Tech Summit で登壇してきました!
11/1,2 で開催された Microsoft Tech Summit でテクニカルスポンサー枠として登壇してきました。
弊社は Mobile 系の開発や Windows 系の開発を推進してきた経緯もあり、今回 日本マイクロソフト株式会社 様より Xamarin でイベントアプリ開発のご依頼を受け、その流れで登壇をさせていただきました。
講演内容
【Xamarin 開発の真実】イベントアプリの中身、すべてお見せします
と名前負けしそうなタイトルとなりましたが、主な内容としては以下の通りです。
- Xamarin とは
- イベントアプリの構成
- Xamarin と UX
- Xamarin と認証
- Xamarin と Push 通知 + α
それぞれどんなことを話したか簡単にまとめます。
Xamarin とは
このサイト見てくださいw
www.jmas.co.jp
イベントアプリの構成
イベントアプリの構成はこんな感じですー。ってそのままの話です。
サーバーは別の会社さんが開発をしていて、そこにアプリから接続してデータのやり取りをしています。
Xamarin と UX
標準だけでいけると思ってましたがやはりカスタマイズが必要になったので、Custom Renderer や Effects を利用しなきゃダメだよってお話し。
Xamarin と 認証
イベントアプリでは Microsoft アカウントでのサインインをする必要があります。
認証機能をどうやって作っているのか、デモを交えて説明しました。
詳細はここを参照ください。
blog.xamarin.com
Xamarin と Push 通知 + α
Azure Mobile Engagement を利用して Push 通知やユーザー状況の確認、クラッシュログなどが簡単に利用できるってお話し。
こんな感じでアクティブなユーザー数がリアルタイムで表示されます。
どの画面がよく使われているかをグラフィカルに表示してくれます。
詳細はここを参照ください。
azure.microsoft.com
※上記リンクは Xamarin.Android 用 & ちょっと古いので、変更が必要な箇所があります。
Azure Mobile Engagement を使ってみて
セッション中も参加者の方々にご協力いただき、アプリをアクティブにしていただいてリアルタイムに数がぐーんと上がるのをお見せしましたが、あんな機能を数行のコードで実現できてしまうのはホントにスゴイと思います。
Push 通知も簡単に管理できます!(デモでは失敗しましたが…)
マーケティングの方にすごい喜ばれる機能が盛りだくさんな感じです。
是非お試しください!!
azure.microsoft.com
Visual Studio から 作成した iOS の ipa をアプリケーションローダーに喰わせると ITMS-90023 が発生する
Xamarin で開発して iOS の AppStore にアップロードするための ipa を作成する際にアプリケーションローダーを使用してます。
Visual Studio の Xamarin.Forms のテンプレートから作ったプロジェクトをそのままアップロードすると以下のエラーが発生します。
ERROR ITMS-90023: "Missing required icon file. The bundle does not contain an app icon for iPad of exactly '167x167' pixels, in .png format for iOS versions supporting iPad Pro."
対応方法
Info.plist を XML エディター等で開きます。
その中のアイコンの指定が並んでいる箇所を選択して
<array> <string>Icon-72@2x.png</string> <string>Icon-72.png</string> <string>Icon@2x.png</string> <string>Icon.png</string> <string>Icon-60@2x.png</string> <string>Icon-76.png</string> <string>Icon-76@2x.png</string> <string>Default.png</string> <string>Default@2x.png</string> <string>Default-568h@2x.png</string> <string>Default-Portrait.png</string> <string>Default-Portrait@2x.png</string> <string>Icon-Small-50@2x.png</string> <string>Icon-Small-50.png</string> <string>Icon-Small-40.png</string> <string>Icon-Small-40@2x.png</string> <string>Icon-Small.png</string> <string>Icon-Small@2x.png</string> </array>
の中に
<string>Icon-83.5@2x.png</string>
を追加します。
あとは対応するサイズ(167x167)のアイコンを追加すればOKです。
注意点
Visual Studio 上で iOS のプロジェクトのプロパティを編集(プロビジョニングファイル等の設定を編集)して保存すると上記のplistの設定が勝手に消えますw
なので、申請用 ipa を作成する際にはちゃんと確認した方がいいかもですね。。
追記
田淵さん(@ytabuchi)に教えていただいた方法の方が楽ちんですね。
ytabuchi.hatenablog.com
(おそらく)世界一簡単にXamarin.Formsのアプリに特定の値でQRコードを生成して表示する方法
public MainPage() { var QrValue = "QRコードにしたい文字列"; var imgQr = new Image { Aspect = Aspect.AspectFit }; imgQr.Source = ImageSource.FromUri(new Uri($"http://chart.apis.google.com/chart?cht=qr&chs=200x200&chld=H|0&chl={QrValue}")); Content = imgQr; }
以上!
というのも寂しいので、ちょっと解説です。
みなさんご存知の通り、XamarinにはURLを画像ソースに指定する機能があります。
developer.xamarin.com
それを利用してGoogleのQRコード生成のAPIを呼び出し、その結果を設定しているだけです。
QR Codes | Infographics | Google Developers
※Warning: This API is deprecated. Please use the actively maintained Google Charts API instead. See our deprecation policy for details.
ということで現在、このAPIは非推奨のようなのでいつ使えなくなるかわかりません。
また、非推奨になったことでライセンスもよくわかりませんでした。
なので自己責任でお願いします
画像として返却してくれるAPIであればこれでなくてもなんでも大丈夫だと思います。
さらにおまけで…
こんな感じでイメージコントロールとインジケーターを重ねておいてイメージの読み込みが完了するまでぐるぐるが表示されるようにするといい感じですね。
(このAPIからのレスポンスが一瞬なのでぐるぐるが見えることはないかもしれません。。。)
public MainPage() { var QrValue = "QRコードにしたい文字列"; var imgQr = new Image { Aspect = Aspect.AspectFit }; imgQr.Source = ImageSource.FromUri(new Uri($"http://chart.apis.google.com/chart?cht=qr&chs=200x200&chl={QrValue}")); var prgIndicator = new ActivityIndicator{ HorizontalOptions = LayoutOptions.CenterAndExpand, VerticalOptions = LayoutOptions.CenterAndExpand }; imgQr.PropertyChanged += (sender, e) => { if (e.PropertyName != nameof(imgQr.IsLoading)) return; prgIndicator.IsRunning = imgQr.IsLoading; prgIndicator.IsVisible = imgQr.IsLoading; }; Content = new Grid { Children = { prgIndicator, imgQr } }; }
Xamarin.FormsでUWPをReleaseビルドすると埋め込みリソースの画像が表示されない
以下の環境でUWPをReleaseビルドするとPCL側に配置した埋め込みリソースの画像が表示されませんでした。
・Xamarin.Forms 2.3.1.114
・VisualStudio 2015 Enterprise Update 3
・UWP対象ターゲット 10586
Xamarin.Formsで画像を表示する際に全プラットフォームで共通の画像を使いまわしたい場合、埋め込みリソースを使用する場合があると思います。
画像の設定方法については公式を参照ください。
developer.xamarin.com
んで、埋め込みリソースを使っていて、かつUWPのReleaseビルド(ビルドプロパティの「.Net ネイティブ ツール チェーンでコンパイルする」にチェックついている状態)で作成したアプリを実行するとデバッグコンソールに以下のエラーが出力されて画像が表示されませんでした。
例外がスローされました: 'System.IO.FileNotFoundException' (System.Private.Reflection.Core.dll の中)
んで、対症療法です。(解決方法と書いていいか自信ない…)
というか、こうやったらできた。
画像のパスを設定する際にAssemblyを引数に加えてやると表示されるようになりました。
var assembly = typeof(App).GetTypeInfo().Assembly; img.Source = ImageSource.FromResource("XamarinApp1.Images.xamarin.png", assembly);
.Net Nativeさん怖い…
Androidでアプリアンインストール時の確認メッセージに「~は次のアプリの一部です」と出てくる
(Android開発に慣れている人は当たり前のことなのかもしれませんが)
Xamarin.Formsでデフォルトの設定で作成したアプリをアンインストールをしようとすると、以下のように謎の確認メッセージがでてきます。
(Android 5以降で確認)
変ですよね。
普通のアプリはこんな感じになります。
それで直し方。
アプリの名前なので、AndroidManifestの記載とMainActivity.csの記載を一致させると期待する結果になりました。
結果
上の例はテスト用の命名でしたが、通常の開発だとプロジェクトの命名にスペースは普通入れないけど、アプリの命名にはスペース入れるってケースは多いと思います。
その場合、MainActivity側を修正しないといけないって点がちょっとハマりました…
(MainActivityの記載はプロジェクト作成時に勝手に作成されて、かつ違いがスペースの有無だけなので…)
"ConvertResourcesCases" タスクが予期せずに失敗しました。
備忘
Xamarinでソリューションのファイルに日本語のパスが含まれているとAndroidのビルド時にタイトルのエラーが出る。
ユーザー名が日本語だと意図せず日本語パスが含まれる可能性もあるから要注意。
エラー "ConvertResourcesCases" タスクが予期せずに失敗しました。
System.IO.DirectoryNotFoundException: パス 'C:\Users\kawai\Documents\Visual Studio 2015\Projects\XamarinTestApp縺・XamarinApp\XamarinApp.Droid\obj\Debug\__library_projects__\Xamarin.Forms.Platform.Android\library_project_imports\res' の一部が見つかりませんでした。
場所 System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
場所 System.IO.FileSystemEnumerableIterator`1.CommonInit()
場所 System.IO.FileSystemEnumerableIterator`1..ctor(String path, String originalUserPath, String searchPattern, SearchOption searchOption, SearchResultHandler`1 resultHandler, Boolean checkHost)
場所 System.IO.Directory.EnumerateDirectories(String path, String searchPattern, SearchOption searchOption)
場所 Xamarin.Android.Tasks.ConvertResourcesCases.FixupResources(ITaskItem item, Dictionary`2 acwMap)
場所 Xamarin.Android.Tasks.ConvertResourcesCases.FixupResources(Dictionary`2 acwMap)
場所 Xamarin.Android.Tasks.ConvertResourcesCases.Execute()
場所 Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
場所 Microsoft.Build.BackEnd.TaskBuilder.d__26.MoveNext() XamarinApp.Droid
Android only allows one navigation page on screen at a time
備忘
Xamarin.FormsのAndroid実行でタイトルのようなエラーが出たら、
だめ
public App() { MainPage = new NavigationPage(new MediaPage()); }
だいじょうぶ
public App() { MainPage = new MainPage(); }
UWPでカメラを使う際の注意点
UWPでカメラを使ったアプリを作る場合、一番簡単なのは CameraCaptureUI を使用した方法です。
細かい使い方はMSDN参照↓
CameraCaptureUI を使った写真とビデオのキャプチャ - Windows app development
この方法であればマニフェストファイルを「Webカメラ」の宣言も不要なままカメラを使えます。
ただ、この方法だと「アプリケーションのテンポラリーフォルダ(ms-appdata:///temp/)」に「CCapture.png」というファイル名でファイルがドンドン溜まっていきます。
なので、気付いたら保存した覚えのない写真がいつまでも残っちゃうなんてことがあるので要注意です。
ファイルにしたくない、細かい設定がしたい、とかの場合は MediaCapture を使用する方がよいみたいです。
MSDN サブスクリプション ユーザーのXamarin登録方法
XamarinがVisualStudioユーザーに無料でついてくるようになりTLがその話題で持ち切りですが、実際にアクティベーションする方法がわかりづらく、また、どこにも書いてなかったのでメモ。
<前提>
Visual Studio Enterprise with MSDN のライセンスの場合です。
その他のサブスクリプションに関してはわかりません。
① MSDNのサブスクリプションのページへ行きます。
https://msdn.microsoft.com/ja-jp/subscriptions/manage
② アカウントタブの「Xamarin Studio (for OS X)」の「Register and download」を選択
③ Xamarinの登録ページに飛ぶので必要事項を記入して登録!!!
無料だーー!
GridView(ListView)のアイテムを選択した時に凹む動作をやめたい
UWPでGridViewもしくはListViewをただ単にパネルとしてボタンとかのコントロールを置いて使いたいケースがありました。
UWPから標準のGridViewItemはPressed時に押した箇所が凹むアニメーションが勝手に入ります。
それがちょっと用途からして邪魔だったので消しました。
MSDNのページからスタイルをコピーしてきて、、
GridViewItem スタイルとテンプレート - Windows app development
悪さをしそうなStoryboardを消してきます。
根本はこいつですね↓
PointerDownThemeAnimation
ついでにPointerOver時に枠に色が付くのとかも消しました。
こんな感じ↓
XAML
一応アイテムクリックが動くのか、アイテムの中のボタンは動くのかの確認用のイベント入れてます。
<GridView IsItemClickEnabled="True" ItemClick="GridView_ItemClick"> <!--動くGridViewItem--> <GridViewItem> <StackPanel Width="300" Height="300"> <StackPanel.Background> <ImageBrush Stretch="Fill" ImageSource="Assets/saru.PNG"/> </StackPanel.Background> <TextBlock FontSize="30" Text="動くGridViewItem" /> <Button Click="Button_Click" Content="テストボタン" /> </StackPanel> </GridViewItem> <!--動かないGridViewItem--> <GridViewItem Style="{StaticResource DisableEffectGridViewItemStyle}"> <StackPanel Width="300" Height="300"> <StackPanel.Background> <ImageBrush Stretch="Fill" ImageSource="Assets/saru.PNG"/> </StackPanel.Background> <TextBlock FontSize="30" Text="動かないGridViewItem" /> <Button Click="Button_Click" Content="テストボタン" /> </StackPanel> </GridViewItem> </GridView>
Style
<Style x:Key="DisableEffectGridViewItemStyle" TargetType="GridViewItem"> <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" /> <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" /> <Setter Property="Background" Value="Transparent" /> <Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}" /> <Setter Property="TabNavigation" Value="Local" /> <Setter Property="IsHoldingEnabled" Value="True" /> <Setter Property="HorizontalContentAlignment" Value="Center" /> <Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="Margin" Value="0,0,4,4" /> <Setter Property="MinWidth" Value="{ThemeResource GridViewItemMinWidth}" /> <Setter Property="MinHeight" Value="{ThemeResource GridViewItemMinHeight}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="GridViewItem"> <Grid x:Name="ContentBorder" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal"> <Storyboard> <PointerUpThemeAnimation Storyboard.TargetName="ContentPresenter" /> </Storyboard> </VisualState> <VisualState x:Name="PointerOver" /> <VisualState x:Name="Pressed" /> <VisualState x:Name="Selected" /> <VisualState x:Name="PointerOverSelected" /> <VisualState x:Name="PressedSelected" /> </VisualStateGroup> <VisualStateGroup x:Name="DisabledStates"> <VisualState x:Name="Enabled" /> <VisualState x:Name="Disabled"> <Storyboard> <DoubleAnimation Duration="0" Storyboard.TargetName="ContentBorder" Storyboard.TargetProperty="Opacity" To="{ThemeResource ListViewItemDisabledThemeOpacity}" /> </Storyboard> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="FocusStates"> <VisualState x:Name="Unfocused" /> <VisualState x:Name="Focused"> <Storyboard> <DoubleAnimation Duration="0" Storyboard.TargetName="FocusVisualWhite" Storyboard.TargetProperty="Opacity" To="1" /> <DoubleAnimation Duration="0" Storyboard.TargetName="FocusVisualBlack" Storyboard.TargetProperty="Opacity" To="1" /> </Storyboard> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="MultiSelectStates"> <VisualState x:Name="MultiSelectDisabled"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="MultiSelectSquare" Storyboard.TargetProperty="Visibility"> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible" /> <DiscreteObjectKeyFrame KeyTime="0:0:0.333" Value="Collapsed" /> </ObjectAnimationUsingKeyFrames> <FadeOutThemeAnimation TargetName="MultiSelectSquare" /> </Storyboard> </VisualState> <VisualState x:Name="MultiSelectEnabled"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="MultiSelectSquare" Storyboard.TargetProperty="Visibility"> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible" /> </ObjectAnimationUsingKeyFrames> <FadeInThemeAnimation TargetName="MultiSelectSquare" /> </Storyboard> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="DataVirtualizationStates"> <VisualState x:Name="DataAvailable" /> <VisualState x:Name="DataPlaceholder"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderTextBlock" Storyboard.TargetProperty="Visibility"> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderRect" Storyboard.TargetProperty="Visibility"> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="ReorderHintStates"> <VisualState x:Name="NoReorderHint" /> <VisualState x:Name="BottomReorderHint"> <Storyboard> <DragOverThemeAnimation Direction="Bottom" ToOffset="{ThemeResource GridViewItemReorderHintThemeOffset}" TargetName="ContentBorder" /> </Storyboard> </VisualState> <VisualState x:Name="TopReorderHint"> <Storyboard> <DragOverThemeAnimation Direction="Top" ToOffset="{ThemeResource GridViewItemReorderHintThemeOffset}" TargetName="ContentBorder" /> </Storyboard> </VisualState> <VisualState x:Name="RightReorderHint"> <Storyboard> <DragOverThemeAnimation Direction="Right" ToOffset="{ThemeResource GridViewItemReorderHintThemeOffset}" TargetName="ContentBorder" /> </Storyboard> </VisualState> <VisualState x:Name="LeftReorderHint"> <Storyboard> <DragOverThemeAnimation Direction="Left" ToOffset="{ThemeResource GridViewItemReorderHintThemeOffset}" TargetName="ContentBorder" /> </Storyboard> </VisualState> <VisualStateGroup.Transitions> <VisualTransition GeneratedDuration="0:0:0.2" To="NoReorderHint" /> </VisualStateGroup.Transitions> </VisualStateGroup> <VisualStateGroup x:Name="DragStates"> <VisualState x:Name="NotDragging" /> <VisualState x:Name="Dragging"> <Storyboard> <DoubleAnimation Duration="0" Storyboard.TargetName="ContentBorder" Storyboard.TargetProperty="Opacity" To="{ThemeResource ListViewItemDragThemeOpacity}" /> <DragItemThemeAnimation TargetName="ContentBorder" /> </Storyboard> </VisualState> <VisualState x:Name="DraggingTarget"> <Storyboard> <DropTargetItemThemeAnimation TargetName="ContentBorder" /> </Storyboard> </VisualState> <VisualState x:Name="MultipleDraggingPrimary"> <Storyboard> <DoubleAnimation Duration="0" Storyboard.TargetName="MultiArrangeOverlayBackground" Storyboard.TargetProperty="Opacity" To="1" /> <DoubleAnimation Duration="0" Storyboard.TargetName="MultiArrangeOverlayText" Storyboard.TargetProperty="Opacity" To="1" /> <DoubleAnimation Duration="0" Storyboard.TargetName="ContentBorder" Storyboard.TargetProperty="Opacity" To="{ThemeResource ListViewItemDragThemeOpacity}" /> <FadeInThemeAnimation TargetName="MultiArrangeOverlayBackground" /> <FadeInThemeAnimation TargetName="MultiArrangeOverlayText" /> <DragItemThemeAnimation TargetName="ContentBorder" /> </Storyboard> </VisualState> <VisualState x:Name="MultipleDraggingSecondary"> <Storyboard> <FadeOutThemeAnimation TargetName="ContentBorder" /> </Storyboard> </VisualState> <VisualState x:Name="DraggedPlaceholder"> <Storyboard> <FadeOutThemeAnimation TargetName="ContentBorder" /> </Storyboard> </VisualState> <VisualStateGroup.Transitions> <VisualTransition GeneratedDuration="0:0:0.2" To="NotDragging" /> </VisualStateGroup.Transitions> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <ContentPresenter x:Name="ContentPresenter" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" ContentTransitions="{TemplateBinding ContentTransitions}" /> <TextBlock x:Name="PlaceholderTextBlock" Margin="{TemplateBinding Padding}" AutomationProperties.AccessibilityView="Raw" Foreground="{x:Null}" IsHitTestVisible="False" Text="Xg" Visibility="Collapsed" /> <Rectangle x:Name="PlaceholderRect" Fill="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}" Visibility="Collapsed" /> <Rectangle x:Name="MultiArrangeOverlayBackground" Grid.ColumnSpan="2" Fill="{ThemeResource ListViewItemDragBackgroundThemeBrush}" IsHitTestVisible="False" Opacity="0" /> <Rectangle x:Name="BorderRectangle" IsHitTestVisible="False" Opacity="0" Stroke="{ThemeResource SystemControlHighlightListAccentLowBrush}" StrokeThickness="2" /> <Border x:Name="MultiSelectSquare" Width="20" Height="20" Margin="0,2,2,0" HorizontalAlignment="Right" VerticalAlignment="Top" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" Visibility="Collapsed"> <FontIcon x:Name="MultiSelectCheck" FontFamily="{ThemeResource SymbolThemeFontFamily}" FontSize="16" Foreground="{ThemeResource SystemControlForegroundBaseMediumHighBrush}" Glyph="" Opacity="0" /> </Border> <Rectangle x:Name="FocusVisualWhite" IsHitTestVisible="False" Opacity="0" Stroke="{ThemeResource SystemControlForegroundAltHighBrush}" StrokeDashArray="1.0, 1.0" StrokeDashOffset="1.5" StrokeEndLineCap="Square" StrokeThickness="2" /> <Rectangle x:Name="FocusVisualBlack" IsHitTestVisible="False" Opacity="0" Stroke="{ThemeResource SystemControlForegroundBaseHighBrush}" StrokeDashArray="1.0, 1.0" StrokeDashOffset="0.5" StrokeEndLineCap="Square" StrokeThickness="2" /> <TextBlock x:Name="MultiArrangeOverlayText" Grid.ColumnSpan="2" Margin="18,9,0,0" AutomationProperties.AccessibilityView="Raw" FontFamily="{ThemeResource ContentControlThemeFontFamily}" FontSize="26.667" Foreground="{ThemeResource ListViewItemDragForegroundThemeBrush}" IsHitTestVisible="False" Opacity="0" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.DragItemsCount}" TextTrimming="WordEllipsis" TextWrapping="Wrap" /> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
Slackでよく使うショートカットとか
メモメモ
Alt + クリック : 既読→未読
入力領域で↑ : 直近の自分の投稿を再編集する
``` で囲む : 囲ったところを引用
> (半角) : その行を引用
Window.Current.Bounds.Height の罠
Mobileで実行しているUWPで画面いっぱいにポップアップを出す際の注意点です。
最初に結論から言うと、
- ポップアップを開く前にcs上でポップアップのサイズを動的に指定して画面いっぱいにする
- Window.Current.Bounds は危険
- Mobileは通知領域やOSボタンを考慮しましょう(GetForCurrentView().VisibleBoundsを使えば大丈夫!)
です。
今回使用したサンプルアプリは一番下にまるまる載せているので、ご参考までに。
サンプルアプリの概要
- 画面上にデバイスファミリー、Window.Current.Bounds(画面サイズ)、ApplicationView.GetForCurrentView().VisibleBoundsのサイズを表示
- アプリをフルスクリーンモードへの切り替え
- Window.Current.Boundsのサイズと同じサイズのポップアップを表示する
- ページの一番上の階層のGridのサイズと同じサイズのポップアップを表示する
では実際に見てみましょう。
PCでサイズの確認
サンプルアプリを動かすと以下のようになります。
タイトルバーやツールバーの有無が変わるので、それぞれのモードで数字は違いますが、
Window.Current.BoundsとVisibleBoundsは両方同じ数字になってますね。
Windowモード時
フルスクリーンモード時
PCでPopup表示
Window.Current.Boundsを設定する方法
ポップアップを開く前にWindow.Current.Boundsをポップアップに設定して表示します。
ポップアップにはテキストを並べてスクロールするようになっています。
「1」を並べて、一番下だけ「2」になっています。
grdPop.Height = Window.Current.Bounds.Height;
grdPop.Width = Window.Current.Bounds.Width;
popTest.IsOpen = true;
当然ながら一番下までスクロールできますね。
VisibleBoundsを設定する方法
最初に確認した通り、数字が一緒なので、同じ結果になります。
同じなので、画像は割愛します。
var view = ApplicationView.GetForCurrentView();
grdPop.Height = view.VisibleBounds.Height;
grdPop.Width = view.VisibleBounds.Width;
popTest.IsOpen = true;
それでは問題のMobileです。
Mobileで確認
同じアプリをNuAns NEOで表示してみます。
フルスクリーンモードでは同じですが、Windowモードの場合、
Window.Current.Bounds.HeightとVisibleBounds.Heightの数字が異なっています。
そうです。
こいつが諸悪の根源です。
Windowモード時
フルスクリーンモード時
Window.Current.Boundsだと、Mobileの通知領域やOS標準のボタン領域も含めた単純な画面サイズが取得されてしまいます。
ApplicationView.GetForCurrentView().VisibleBounds だと、現在のビューに表示されている領域のサイズが取得されます。
そのため、このような差が生まれてしまうのですね。
では続いてポップアップを表示してみましょう。
MobileでPopup表示
Window.Current.Boundsを設定する方法
最終行である「2」がボタン領域の下に隠れてしまっています。
VisibleBoundsを設定する方法
正常にすべて表示されます。
まとめ
PCでは同じだけど、Mobileだと異なるっていうのがいやらしいですね。
ウチのアプリの場合、PC版を作って後からMobileに対応ってパターンがあるので、ハマりがちです。
全部見直さないと。。。。。
ソース
MainPage.xaml.cs
using Windows.System.Profile; using Windows.UI.ViewManagement; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Input; namespace WindowSizeTest { public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); } private void grdMain_SizeChanged(object sender, SizeChangedEventArgs e) { //Page_SizeChangeはなぜかMobileのFullスクリーンモードの切り替えだと動いてくれないため、 //GridのSizeChangeでTextの再設定を行う setText(); } private void ToggleButton_Checked(object sender, RoutedEventArgs e) { changeViewMode(true); } private void ToggleButton_Unchecked(object sender, RoutedEventArgs e) { changeViewMode(false); } private void btnShowPopupWindowBounds_Click(object sender, RoutedEventArgs e) { grdPop.Height = Window.Current.Bounds.Height; grdPop.Width = Window.Current.Bounds.Width; popTest.IsOpen = true; } private void btnShowPopupVisibleBounds_Click(object sender, RoutedEventArgs e) { var view = ApplicationView.GetForCurrentView(); grdPop.Height = view.VisibleBounds.Height; grdPop.Width = view.VisibleBounds.Width; popTest.IsOpen = true; } private void grdPop_Tapped(object sender, TappedRoutedEventArgs e) { popTest.IsOpen = false; } private void changeViewMode(bool isFull) { var view = ApplicationView.GetForCurrentView(); if (isFull) { view.TryEnterFullScreenMode(); } else { view.ExitFullScreenMode(); } } private void setText() { txtDevice.Text = AnalyticsInfo.VersionInfo.DeviceFamily; txtWindowHeight.Text = Window.Current.Bounds.Height.ToString(); txtWindowWidth.Text = Window.Current.Bounds.Width.ToString(); var view = ApplicationView.GetForCurrentView(); txtVisibleBoundsHeight.Text = view.VisibleBounds.Height.ToString(); txtVisibleBoundsWidth.Text = view.VisibleBounds.Width.ToString(); } } }
MainPage.xaml
<Page x:Class="WindowSizeTest.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:WindowSize" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Page.Resources> <Style TargetType="TextBlock"> <Setter Property="FontSize" Value="28" /> </Style> <Style x:Key="txtValueStyle" TargetType="TextBlock"> <Setter Property="FontSize" Value="28" /> <Setter Property="Foreground" Value="Red" /> </Style> <Style TargetType="Button"> <Setter Property="Margin" Value="10" /> <Setter Property="FontSize" Value="28" /> </Style> <Style TargetType="ToggleButton"> <Setter Property="FontSize" Value="28" /> </Style> </Page.Resources> <Grid x:Name="grdMain" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" SizeChanged="grdMain_SizeChanged"> <Viewbox VerticalAlignment="Top"> <Grid x:Name="grdContent"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <StackPanel Margin="10,0"> <TextBlock Text="AnalyticsInfo.VersionInfo.DeviceFamily" /> <TextBlock Text="Window.Current.Bounds.Height" /> <TextBlock Text="Window.Current.Bounds.Width" /> <TextBlock Text="GetForCurrentView().VisibleBounds.Height" /> <TextBlock Text="GetForCurrentView().VisibleBounds.Width" /> <ToggleButton Margin="10" Checked="ToggleButton_Checked" Content="FullScreen" Unchecked="ToggleButton_Unchecked" /> <Button x:Name="btnShowPopupWindowBounds" Click="btnShowPopupWindowBounds_Click" Content="ShowPopupWindowBounds" /> <Button x:Name="btnShowPopupVisibleBounds" Click="btnShowPopupVisibleBounds_Click" Content="ShowPopupVisibleBounds" /> </StackPanel> <StackPanel Grid.Column="1"> <TextBlock x:Name="txtDevice" Style="{StaticResource txtValueStyle}" /> <TextBlock x:Name="txtWindowHeight" Style="{StaticResource txtValueStyle}" /> <TextBlock x:Name="txtWindowWidth" Style="{StaticResource txtValueStyle}" /> <TextBlock x:Name="txtVisibleBoundsHeight" Style="{StaticResource txtValueStyle}" /> <TextBlock x:Name="txtVisibleBoundsWidth" Style="{StaticResource txtValueStyle}" /> </StackPanel> </Grid> </Viewbox> <Popup x:Name="popTest"> <Grid x:Name="grdPop" Background="AliceBlue" Tapped="grdPop_Tapped"> <ScrollViewer> <StackPanel> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="1" /> <TextBlock Text="2" /> </StackPanel> </ScrollViewer> </Grid> </Popup> </Grid> </Page>
Raspberry Pi 2 Model B に Windows 10 IoT Core for Raspberry Pi 2 をインストールする
毎年、弊社では社内ハッカソンを行っているのですが、私はWindows系が本業なのでそれを生かしつつ、ちょっと新しいことをやりたいなと。
そして昨年のde:Codeの抽選で当たったラズパイが埋もれていたのでこいつを使うことにしました。
とりあえず環境構築を行いました。
(何を作るかはまた後日。。。)
諸々を購入
本体はあるものの、周辺機器が何もないので、社内ハッカソン経費で以下を購入
・無線LANアダプター(※後述しますが、これは使用しませんでした)
BUFFALO 11n対応 11g/b 無線LAN子機 親機-子機デュアルモード対応モデル WLI-UC-GNM2
- 出版社/メーカー: バッファロー
- 発売日: 2011/07/30
- メディア: Personal Computers
- 購入: 198人 クリック: 5,602回
- この商品を含むブログを見る
・SDカード
- 出版社/メーカー: トランセンド・ジャパン
- 発売日: 2015/10/02
- メディア: Personal Computers
- この商品を含むブログを見る
・ディスプレイ
・カメラ
Raspberry Pi Video Module Raspberry Pi Camera Board 775-7731
- 出版社/メーカー: Raspberry Pi
- メディア: 付属品
- この商品を含むブログ (1件) を見る
・色々入っているキット
Raspberry Pi電子工作エントリーキット(Premium)
- 出版社/メーカー: TechShare
- メディア: エレクトロニクス
- この商品を含むブログを見る
Raspberry Pi Model B+ / Pi2用 ヒートシンクセット
- 出版社/メーカー: TechShare
- メディア: エレクトロニクス
- この商品を含むブログを見る
・LEGO(本体ケースやカメラケースを自作しようかなと思い)
レゴ クラシック アイデアパーツ<エクストラセット> 10702
- 出版社/メーカー: レゴ
- 発売日: 2016/01/15
- メディア: おもちゃ&ホビー
- この商品を含むブログを見る
SDカードにWindows 10 IoT Core for Raspberry Pi 2をインストール
色々調べた結果、「raspberry pi windows10」でググると一番上にくる(2016/1/26時点)↓のサイトの様にPCでファイルをダウンロードして、イメージファイルをどうのこうの。。。とかは全く必要ありませんでした。
ascii.jp
Microsoftさんが、丁寧にまとめてくれていたので、こちらを参考にしました。
ms-iot.github.io
PCでこのサイトの真ん中へんの「Get the Windows 10 IoT Core Dashboard」をクリック
すると、IoT端末を管理するためのデスクトップアプリがPCにインストールされます。
インストール後、起動すると、、
え?勝手にやってくれる系?
すごい簡単そう。。。
んで、指示通りにSDカードをPCに接続し、インストール実行!
終わったけど、ちゃんとやってくれたのか不安を抱えながらラズパイにSDカードを接続して起動してみました。
ちなみにラズパイの起動は電源を挿すと起動します。
起動した!!
簡単過ぎ!!!
ネットワークに接続
インストールはできたけど、Windows 10 IoTはネットワークにつないでPCと通信ができないと何もできません。
※私も勘違いしていたのですが、「Windows」と銘打つからにはデスクトップがあって、エクスプローラーがあって、、とかと想像してましたが違います。
(そうなっていた方が「Windows」としての強み、他のOSとの差別化としてはいいと思うんですがね。。)
なので、ネットワークにつなぎます。
ここで無線LANアダプターの登場です。
「Raspberry Pi 無線LAN」とかでググるとたくさん出てきたので、(どうせ会社の金だし!と)あまり調べず購入したのですが、結論から言うと使えませんでした。
挿しても認識してくれません。
Raspbian OSだと大丈夫の様ですが、Windows 10 IoTでは
ms-iot.github.io
こちらに書いてある機器しかサポートしていないらしい。。。
そのため、なぜか会社にあったコイツ↓を使用してエセ無線LAN環境を構築しました。不幸中の幸いです。
これはちょっと高いですが、コンバーターとして使えるラズパイとLANケーブルでつなぐようなモノは探せばもっと安いのがあると思います。
- 出版社/メーカー: プラネックス
- 発売日: 2013/11/29
- メディア: Personal Computers
- この商品を含むブログ (1件) を見る
PCと同じネットワークに接続して、こちらに書いてある通りにPCからラズパイにアクセスします。
ms-iot.github.io
・ラズパイからIPアドレスを確認
・IPアドレス+「:8080」をブラウザでたたく
・ユーザーID:Administrator、パスワード:p@ssw0rdで接続
以上でラズパイの管理画面に接続できました。
めでたし、めでたし。
後日、作成したアプリとかをインストールしたいと思います。
※※※※※※※※※※※※※※※※※
2016/1/29 追記
カメラモジュールも使えませんでした...orz
Windows 10 Mobile でアプリケーションのフォルダーを参照したい
=================================================
追記:最新のバージョンでは下記手順はできなくなってます。
どうしよう。。。
=================================================
UWPの開発で ApplicationData.Current.LocalFolder(= ms-appdata:///local/) とかにログを出力したりするケースがあると思います。
ですが、Windows 10 Mobileの標準エクスプローラーでは大したところは見えません。
↓ このぐらいです。
検索しても見つけてくれません。
なので、どうにかしてログファイルをみたいな、と。
調べてみたところ、以下のサイトに行きつきました。
<参考サイト>
参考にして試行錯誤の末、なんとかできましたのでまとめときます。
もっとスマートな方法があったら教えてください。。。
1.PCでCドライブ直下のショートカットを作成する
なんでやねん。
なんでMobileってゆーのにいきなりPCが登場するんじゃい。
と突っ込みを入れたくなりますが、そこはこらえてください。
- Cドライブを右クリック
- ショートカットの作成
2.作成したショートカットをMobileに移動する
参考サイトの通り、実機でやるとUSBで簡単なのですが、今回はエミュレーターでの手順を記載しておきます。
- エミュレーターのメニューから≫を選択
- SD Card タブを選択
- ショートカットを置いた適当なフォルダーを指定
- Insert SD Card を選択
- Mobile 標準のエクスプローラーを起動する
- SDCARD が生まれているので選択
- 作成したショートカットがある
これで、Windows 10 Mobile のCドライブ直下を参照することができました。
3.アプリケーションのフォルダーを見つける
あとは簡単じゃーん。と思いの方もいるかもしれません。
そう思っていた時期が私にもありました。
答えから言うと、インストールしたパッケージたちは
C:\Data\Users\DefApps\AppData\Packages
にあります。
では見てみましょう。
- Cドライブ直下
- Data フォルダ
- Users フォルダ
- DefApps フォルダ
- AppData フォルダ
はい、空ですー!
隠しフォルダなのでしょうかね。
ということで・・・
- DefApps フォルダに戻って検索ボタン
- 「Package」と検索するとヒット!!
- お目当てのアプリのパッケージ名を選択
LocalStateがありましたよー!!
あとはふつうに中に入っているログファイルでもなんでも見れる!!
こんな面倒なことしないといけないのでしょうか。。。
ひとまずよかった。