Xamarin.Formsでページの壁紙をイイ感じに表示したい
今日(2018/12/12)時点の最新のXamarin.Forms(not Preview)でのお話
例によって正しいかどうかはご自身で判断してください
- 対象はiOS(Androidは確認してないから知らない)
- Emulatorでのみ動作確認
- Visual Studio 2017でXamarin.Formsのプロジェクト作ってそのテンプレートをいじって試してみる
- いいかんじ = アスペクト比固定でFill
です
ContentPage.BackgroundImageに指定
375 x 812ピクセルの壁紙をiPhone XSのエミュレーター使って確認します
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:BackgroundImageTest" x:Class="BackgroundImageTest.MainPage" BackgroundImage="bg375.jpg"> <StackLayout> <!-- Place new controls here --> <Label Text="Welcome to Xamarin.Forms!" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" /> </StackLayout> </ContentPage>
結果
なんかよさそうですね
壁紙のサイズを100 x 178ピクセルにして試す
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:BackgroundImageTest" x:Class="BackgroundImageTest.MainPage" BackgroundImage="bg100.jpg"> 以下略 </ContentPage>
結果
はい
ということでContentPage.BackgroundImage
はあきらめました (BackgroundImageを制御するものが見つからなかったので)
壁紙のサイズを端末のサイズに合わせればいいじゃんと思うかもしれませんが、Androidに限らずiPhoneも今やいろんなアスペクト比の端末があるんでダメですね
Imageを使う
ググると大体この方法ですね
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:BackgroundImageTest" x:Class="BackgroundImageTest.MainPage"> <Grid> <Image Aspect="AspectFill" Source="bg100.jpg"/> <StackLayout> <!-- Place new controls here --> <Label Text="Welcome to Xamarin.Forms!" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" /> </StackLayout> </Grid> </ContentPage>
結果
今度こそよさそう?
UseSafeAreaを指定する
iPhone Xが出てできた概念ですかね?どういうものかはUseSafeArea
でググればすぐに画像とかでてくると思います。このプロパティを有効にしておけばノッチとかその辺を除いたサイズでどうにかしてくれるってやつですね
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:BackgroundImageTest" x:Class="BackgroundImageTest.MainPage" xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core" ios:Page.UseSafeArea="True" > <Grid> <Image Aspect="AspectFill" Source="bg375.jpg"/> 以下略
結果
UseSafeArea
の効果がよくわかりますね (
ダメだけどContentPage.BackgroundImageで試してみる
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:BackgroundImageTest" x:Class="BackgroundImageTest.MainPage" xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core" ios:Page.UseSafeArea="True" BackgroundImage="bg375.jpg" > 以下略
結果
こっちでイキたいけどいイケないもどかしさ
ImageをContentの外側まで引き延ばす
これが現在の私の答えです
MainPage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:BackgroundImageTest" x:Class="BackgroundImageTest.MainPage" xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core" ios:Page.UseSafeArea="True" > <Grid> <Image x:Name="BgImage" Aspect="AspectFill" Source="bg375.jpg"/> 以下略
MainPage.xaml.cs
protected override void OnAppearing() { base.OnAppearing(); var inset = On<iOS>().SafeAreaInsets(); BgImage.Margin = new Thickness(-inset.Left, -inset.Top, -inset.Right, -inset.Bottom); }
結果
他の端末でもいい感じに壁紙してくれるんじゃないでしょうか(多分)
Azure Functionsに排他制御を入れる
FunctionsでFunction単位で排他制御したいことがあったので調べてみました
結論はBLOB作ってそれをロックでした(他にあるのかしらない)
BLOBのロックは
Microsoft Azure Storage でのコンカレンシー制御の管理 | Microsoft Docs
ここに書いてあります
ということで実験しましょう
HttpTriggerなFunctionを作る
こんな感じ
public static class Function1 { [FunctionName("Function1")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, [Table("Log")] CloudTable logTable, ILogger log) { string name = req.Query["name"]; for (var i = 0; i < 5; i++) { await logTable.ExecuteAsync(TableOperation.Insert(new LogEntity(name, i))); await Task.Delay(1000); } return new OkResult(); } public class LogEntity : TableEntity { public string Name { get; set; } public int Num { get; set; } public LogEntity() { PartitionKey = "Log"; RowKey = Guid.NewGuid().ToString(); } public LogEntity(string name, int num) : this() { Name = name; Num = num; } } }
テンプレートをいじってテーブルストレージ(Log)にデータを保存します
同時制御したいので書き込んで1秒待って、を5回繰り返します
あとはデバッグ実行して
http://localhost:7071/api/Function1?name=a http://localhost:7071/api/Function1?name=b
で呼び出し
書き込んだ値を時間でソートすると
二つのリクエストが同時に走っていることがわかると思います
排他制御を入れて順番に実行する
Run
関数にBLOBのロック処理を追加します
public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, [Table("Log")] CloudTable logTable, Binder binder, ILogger log) { string name = req.Query["name"]; // BLOBをロックすることで排他制御とする #region リース取得 var lockBlob = await binder.BindAsync<CloudBlockBlob>(new BlobAttribute("temp/lock")); if (!await lockBlob.ExistsAsync()) await lockBlob.UploadTextAsync("lock"); // 無ければ作っているけど事前に作っておいた方がいいと思う var lease = ""; for (var i = 0; i < 100; i++) // 20秒挑戦してダメなら終わろう { try { lease = await lockBlob.AcquireLeaseAsync(TimeSpan.FromSeconds(15), null); Console.WriteLine($"blob lease = {lease}"); break; } catch (StorageException storageException) { if (storageException.RequestInformation.HttpStatusCode == (int)HttpStatusCode.Conflict) { Console.WriteLine("Wait for release lease..."); await Task.Delay(200); continue; } return new ObjectResult("Error") { StatusCode = (int)HttpStatusCode.Conflict }; } } if (string.IsNullOrEmpty(lease)) { // 取れなかったらエラーで終わる log.LogWarning("Can't get lease."); return new ObjectResult("Error") { StatusCode = (int)HttpStatusCode.Conflict }; } var accessCondition = AccessCondition.GenerateLeaseCondition(lease); #endregion for (var i = 0; i < 5; i++) { await logTable.ExecuteAsync(TableOperation.Insert(new LogEntity(name, i))); await Task.Delay(1000); } // リース解放 await lockBlob.ReleaseLeaseAsync(accessCondition); return new OkResult(); }
実行結果
順番に実行されるようになりました
注意点とか
コピペすると行数が膨らんで楽しい
BLOBのリース取得部分をクラスにしてもっと簡単に使えるようにしていた人がいました。探せば出てくると思うよ
リース期間に注意
AcquireLeaseAsync
に渡せるのは確か15~60秒だったはずなので注意
リース取得のリトライに注意
Functionsはデフォルトで5分長くても10分動き続けると強制的に落とされるので(Consumptionプラン時だけ?)何も考えずにロックしまくると大変だよ
結論
排他制御はやればできる
けどやらないに越したことはない
フォルダ名の最後がスペースのフォルダを作る (作れない)
作れません
知っている人は当たり前だと思うかもしれませんが、そんな名前のフォルダ作ろうなんて思ったこともなかったので知らなかったよ
試してみる
こうすると
こうなります。勝手に最後のスペースが消されます。警告とかそーいうの出ません
勝手に省略しないで
こっちの仲間に入れてあげればいいのに
C#でやってみる
// 最後がスペースのパス var path = @"g:\test "; try { Directory.CreateDirectory(path); // これは正常終了する } catch { Console.WriteLine("しっぱい"); // 例外とか吐かない } if (Directory.Exists(path)) // これもtrueが返ってくる { var file = Path.Combine(path, "a.txt"); // あるはずフォルダの下にテキストを書いてみる try { File.WriteAllText(file, "にゃーん"); } catch (Exception e) { Console.WriteLine(e.Message); // 「パス 'g:\test \a.txt' の一部が見つかりませんでした。」 } }
Oh...
せめてDirectory.Exists()
はfalse
を返してほしいんだけど・・なんか歴史的な経緯とかあるのかな~