最近、Polylineで描画している際にちょっと不思議(?)な現象に出会ったので、一応メモ程度に。
2015年4月11日
[WPF]描画パフォーマンスのお話(Polylineの場合)
最近、Polylineで描画している際にちょっと不思議(?)な現象に出会ったので、一応メモ程度に。
2015年4月6日
2015年3月10日
【ReactiveProperty】きちんとDisposeしよう
MainWindow.xaml
<Window x:Class="WpfApplication3.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Viewbox> <TextBlock Text="{Binding DateTime.Value}" /> </Viewbox> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WpfApplication3 { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { private IDisposable _disposable; public MainWindow() { InitializeComponent(); var vm = new MainViewModel(); this.DataContext = vm; this._disposable = vm as IDisposable; this.Closing += (s, e) => { if (this._disposable != null) this._disposable.Dispose(); }; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Text; using System.Threading.Tasks; using Reactive.Bindings; namespace WpfApplication3 { public class MainViewModel : IDisposable { public ReactiveProperty<string> DateTime { get; private set; } public MainViewModel() { this.DateTime = Observable.Interval(TimeSpan.FromMilliseconds(10), NewThreadScheduler.Default) .Select(_ => System.DateTime.Now.ToString("HH:mm:ss.fff")).ToReactiveProperty(); } void IDisposable.Dispose() { this.DateTime.Dispose(); } } }
この場合は、NewThreadSchedulerによって新しいThreadが立っているので当然の結果なのですが、Rxを使うと簡単にThreadを起こせるので、神経質にDisposeしていたほうが良い気がしました。
ViewModelBase.cs
using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Disposables; using System.Text; using System.Threading.Tasks; namespace WpfApplication3 { public abstract class ViewModelBase : IDisposable { protected CompositeDisposable Disposable { get; private set; } public ViewModelBase() { this.Disposable = new CompositeDisposable(); } void IDisposable.Dispose() { this.Disposable.Dispose(); } } }
これを利用すると、MainViewModelが次のように少し簡易になります。
MainViewModel.cs
using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Text; using System.Threading.Tasks; using Reactive.Bindings; namespace WpfApplication3 { public class MainViewModel : ViewModelBase { public ReactiveProperty<string> DateTime { get; private set; } public MainViewModel() { this.DateTime = Observable.Interval(TimeSpan.FromMilliseconds(10), NewThreadScheduler.Default) .Select(_ => System.DateTime.Now.ToString("HH:mm:ss.fff")) .ToReactiveProperty() .AddTo(this.Disposable); // かずきさん指摘により修正 // this.Disposable.Add(this.DateTime); } } }
public static class ViewModelBaseEx { public static ReactiveProperty<T> ToReactiveProperty<T>(this IObservable<T> souce, CompositeDisposable disposable) { var rp = souce.ToReactiveProperty(); disposable.Add(rp); return rp; } }
2015年3月9日
Thicknessの125000倍の長さのGeometryをOnRenderで描画
その際は、少し回避方法が異なります。(というか、かなり謎です)
protected override void OnRender(DrawingContext drawingContext) { var geometry = new StreamGeometry(); using (var context = geometry.Open()) { context.BeginFigure(new Point(0, 100), false, false); context.LineTo(new Point(125000, 100), true, false); } drawingContext.DrawGeometry(null, new Pen(Brushes.Black, 1), geometry); base.OnRender(drawingContext); }
protected override void OnRender(DrawingContext drawingContext) { var geometry = new StreamGeometry(); using (var context = geometry.Open()) { context.BeginFigure(new Point(0, 100), false, false); context.LineTo(new Point(125000.1, 100), true, false); } drawingContext.DrawGeometry(null, new Pen(Brushes.Black, 1), geometry); base.OnRender(drawingContext); }
protected override void OnRender(DrawingContext drawingContext) { var geometry = new StreamGeometry(); using (var context = geometry.Open()) { context.BeginFigure(new Point(0, 100), false, false); context.LineTo(new Point(125000.1, 100.1), true, false); } drawingContext.DrawGeometry(null, new Pen(Brushes.Black, 1), geometry); base.OnRender(drawingContext); }
protected override void OnRender(DrawingContext drawingContext) { var geometry = new StreamGeometry(); using (var context = geometry.Open()) { context.BeginFigure(new Point(0, 100), false, false); context.LineTo(new Point(0.1, 100), true, false); } var geometry2 = new StreamGeometry(); using (var context = geometry2.Open()) { context.BeginFigure(new Point(0, 200), false, false); context.LineTo(new Point(375000, 200), true, false); } drawingContext.DrawGeometry(null, new Pen(Brushes.Transparent, 1), geometry); drawingContext.DrawGeometry(null, new Pen(Brushes.Red, 1), geometry2); base.OnRender(drawingContext); }
Thicknessの125000倍の長さのLine
<Window x:Class="TooLongLine.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <!-- 水平LineはStrokeThicknessの12500倍以下しか描画できない --> <!-- 125000倍ちょうどなので描画可能 --> <Line X1="0" X2="125000" Y1="5" Y2="5" StrokeThickness="1" Stroke="Red" /> <!-- 125000倍を超えるので描画不可能 --> <Line X1="0" X2="125000.1" Y1="15" Y2="15" StrokeThickness="1" Stroke="Red" /> <!-- Thicknessを2にすると、125000倍以下になるので描画可能 --> <Line X1="0" X2="125000.1" Y1="25" Y2="25" StrokeThickness="2" Stroke="Blue" /> <!-- 125000倍ちょうどなので描画可能 --> <Line X1="0" X2="250000" Y1="35" Y2="35" StrokeThickness="2" Stroke="Blue" /> <!-- 125000倍を超えるので描画不可能 --> <Line X1="0" X2="250000.1" Y1="45" Y2="45" StrokeThickness="1" Stroke="Blue" /> <!-- 水平でなければ描画可能 --> <Line X1="0" X2="250000.1" Y1="55" Y2="55.001" StrokeThickness="1" Stroke="Green" /> <!-- Polylineで、一つの線分が125000倍以下でも水平になってしまうとダメ --> <Polyline Points="0,65 100000,65 200000,65" StrokeThickness="1" Stroke="Orange" /> </Grid> </Window>
その前に、本当にそんなに長いLineが必要かは要検討ですねw
http://stackoverflow.com/questions/13731593/horizontal-or-vertical-wpf-lines-limited-to-125-000-pixels
2015年2月26日
Gridからの脱出
<Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Canvas Background="Ivory" Margin="50"> <TextBlock Text="abcdefghi" FontSize="100"/> </Canvas> </Window>
ちなみに、ClipToBoundというプロパティーが用意されていて、これをTrueに設定することで、Panelの大きさの外にはみ出した部分が切り取られます。
<Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Canvas Background="Ivory" Margin="50" ClipToBounds="True"> <TextBlock Text="abcdefghi" FontSize="100"/> </Canvas> </Window>
<Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid Background="Ivory" Margin="50" ClipToBounds="False"> <TextBlock Text="abcdefghi" FontSize="100"/> </Grid> </Window>
# 決して同じ画像ではありませんw
<Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid Background="Ivory" Margin="50"> <Canvas Background="AliceBlue"> <TextBlock Text="abcdefghi" FontSize="100"/> </Canvas> </Grid> </Window>
2015年1月16日
WPFでBingMapを使う(その2)
本編の前に、一つ訂正。
「Bing Map WPF Controlは、NuGetで提供されてないのでとっとと対応すべき。」みたいなことを書きましたが、どうやら提供されていた(過去形)ようです。現在は提供されなくなっていますが、いったい何が。
実行時例外
例えば、こんな感じのXAMLを用意します。
<Window x:Class="BingMapSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wpf="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF" Title="MainWindow" Height="350" Width="525"> <Grid> <ContentControl> <ContentControl.Template> <ControlTemplate> <wpf:Map CredentialsProvider="ここにAPIキーを入力"/> </ControlTemplate> </ContentControl.Template> </ContentControl> </Grid> </Window>
System.Windows.Markup.XamlParseException はハンドルされませんでした。
HResult=-2146233087
Message=指定されたバインディング制約に一致する型 ‘Microsoft.Maps.MapControl.WPF.Map’ のコンストラクターの呼び出しで例外がスローされました。
Source=PresentationFramework
LineNumber=0
LinePosition=0
StackTrace:
場所 System.Windows.FrameworkTemplate.LoadTemplateXaml(XamlReader templateReader, XamlObjectWriter currentWriter)
場所 System.Windows.FrameworkTemplate.LoadTemplateXaml(XamlObjectWriter objectWriter)
場所 System.Windows.FrameworkTemplate.LoadOptimizedTemplateContent(DependencyObject container, IComponentConnector componentConnector, IStyleConnector styleConnector, List`1 affectedChildren, UncommonField`1 templatedNonFeChildrenField)
場所 System.Windows.FrameworkTemplate.LoadContent(DependencyObject container, List`1 affectedChildren)
場所 System.Windows.StyleHelper.ApplyTemplateContent(UncommonField`1 dataField, DependencyObject container, FrameworkElementFactory templateRoot, Int32 lastChildIndex, HybridDictionary childIndexFromChildID, FrameworkTemplate frameworkTemplate)
場所 System.Windows.FrameworkTemplate.ApplyTemplateContent(UncommonField`1 templateDataField, FrameworkElement container)
場所 System.Windows.FrameworkElement.ApplyTemplate()
場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
場所 System.Windows.UIElement.Measure(Size availableSize)
場所 System.Windows.Controls.Grid.MeasureOverride(Size constraint)
場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
場所 System.Windows.UIElement.Measure(Size availableSize)
場所 MS.Internal.Helper.MeasureElementWithSingleChild(UIElement element, Size constraint)
場所 System.Windows.Controls.ContentPresenter.MeasureOverride(Size constraint)
場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
場所 System.Windows.UIElement.Measure(Size availableSize)
場所 System.Windows.Documents.AdornerDecorator.MeasureOverride(Size constraint)
場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
場所 System.Windows.UIElement.Measure(Size availableSize)
場所 System.Windows.Controls.Border.MeasureOverride(Size constraint)
場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
場所 System.Windows.UIElement.Measure(Size availableSize)
場所 System.Windows.Window.MeasureOverrideHelper(Size constraint)
場所 System.Windows.Window.MeasureOverride(Size availableSize)
場所 System.Windows.FrameworkElement.MeasureCore(Size availableSize)
場所 System.Windows.UIElement.Measure(Size availableSize)
場所 System.Windows.Interop.HwndSource.SetLayoutSize()
場所 System.Windows.Interop.HwndSource.set_RootVisualInternal(Visual value)
場所 System.Windows.Interop.HwndSource.set_RootVisual(Visual value)
場所 System.Windows.Window.SetRootVisual()
場所 System.Windows.Window.SetRootVisualAndUpdateSTC()
場所 System.Windows.Window.SetupInitialState(Double requestedTop, Double requestedLeft, Double requestedWidth, Double requestedHeight)
場所 System.Windows.Window.CreateSourceWindow(Boolean duringShow)
場所 System.Windows.Window.CreateSourceWindowDuringShow()
場所 System.Windows.Window.SafeCreateWindowDuringShow()
場所 System.Windows.Window.ShowHelper(Object booleanBox)
場所 System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
場所 MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
場所 System.Windows.Threading.DispatcherOperation.InvokeImpl()
場所 System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
場所 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
場所 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
場所 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
場所 System.Windows.Threading.DispatcherOperation.Invoke()
場所 System.Windows.Threading.Dispatcher.ProcessQueue()
場所 System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
場所 MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
場所 MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
場所 System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
場所 MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
場所 System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
場所 MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
場所 MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
場所 System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
場所 System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
場所 System.Windows.Threading.Dispatcher.Run()
場所 System.Windows.Application.RunDispatcher(Object ignore)
場所 System.Windows.Application.RunInternal(Window window)
場所 System.Windows.Application.Run(Window window)
場所 System.Windows.Application.Run()
場所 BingMapSample.App.Main() 場所 c:\Users\******\Documents\Visual Studio 2013\Projects\BingMapSample\BingMapSample\obj\Debug\App.g.cs:行 0
場所 System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
場所 System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
場所 Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
場所 System.Threading.ThreadHelper.ThreadStart_Context(Object state)
場所 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
場所 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
場所 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
場所 System.Threading.ThreadHelper.ThreadStart()
InnerException: System.InvalidOperationException
HResult=-2146233079
Message=ディスパッチャーの処理の中断中は、この操作を実行できません。
Source=WindowsBase
StackTrace:
場所 System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
場所 System.Windows.Threading.DispatcherOperation.Wait(TimeSpan timeout)
場所 System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherOperation operation, CancellationToken cancellationToken, TimeSpan timeout)
場所 System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
場所 System.Windows.Threading.Dispatcher.Invoke(Delegate method, Object[] args)
場所 Microsoft.Maps.MapControl.WPF.Core.MapConfigurationFromWeb.ConfigLoaded(String requestKey, Dictionary`2 sections)
場所 Microsoft.Maps.MapControl.WPF.Core.MapConfigurationFromWeb.GetConfigurationSection(String version, String sectionName, String culture, MapConfigurationCallback callback, Boolean reExecuteCallback, Object userState)
場所 Microsoft.Maps.MapControl.WPF.Core.MapConfiguration.GetSection(String version, String sectionName, String culture, String key, MapConfigurationCallback callback, Boolean reExecuteCallback, Object userState)
場所 Microsoft.Maps.MapControl.WPF.Core.MapConfiguration.GetSection(String version, String sectionName, String culture, String key, MapConfigurationCallback callback, Boolean reExecuteCallback)
場所 Microsoft.Maps.MapControl.WPF.Map..ctor()
InnerException:
何故Dispatcherが中断中かは非常に謎ですが、Dispatcher外で初期化処理が呼び出されているようです。
Templateに含まれるコントロールの作成はDispatcher上で実行されているはずなのですが…。
回避
日本語エラーメッセージをこの辺で検索して、英語でのエラーメッセージに変換し、さらに英語のエラーメッセージで検索すると比較的解決方法はすぐ見つかります。
わかりやすいのは、この辺でしょうか?
要するに、Dispatcher上で非同期実行できるようにBeginInvokeで囲んであげればよいよということですね。
なるほど、では早速適用を…。ちょっと待てよ、初期化処理自体がXAMLで書かれてるのに、BeginInvokeで囲むとかどうするのよ。
決着
でも、TemplateはXAMLで書きたいですよね。あと、Templateの種類によってはコードでは完全には再現できません。
なので、UserControlでWrapしてみました。
/// <summary> /// UserControl1.xaml の相互作用ロジック /// </summary> public partial class UserControl1 : UserControl { public UserControl1() { InitializeComponent(); App.Current.Dispatcher.BeginInvoke( new Action(() => this.Content =new Map { CredentialsProvider =new ApplicationIdCredentialsProvider("ここにAPIキーを入力") })); } }
<Window x:Class="BingMapSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:bingMapSample="clr-namespace:BingMapSample" Title="MainWindow" Height="350" Width="525"> <Grid> <ContentControl> <ContentControl.Template> <ControlTemplate> <bingMapSample:UserControl1/> </ControlTemplate> </ContentControl.Template> </ContentControl> </Grid> </Window>
#なんだろう。解決したのに、この敗北感は。
2015年1月14日
WPFでBingMapを使う
WPFで地図表示がしたい
と思って探しているとやっぱりありました。
Bing Maps WPF Control
しかし、いまどきmsiでインストールしなければいけないとかちょっと残念感が漂う感じ。(NuGetからインストールできるようになるとよいですね。)
というわけで、msiを落としてきてインストールすれば準備完了です。(あ、あとBingMapのAPI Keyも取得しておいてください)
地図表示アプリの作成
# なお、ここからの作業はすべて、VisualStudio2013 & Windows8.1で行ってます。
-
新規プロジェクト作成→参照設定追加C:\Program Files (x86)\Bing Maps WPF Control\V1\Libraries にインストールされた、Microsoft.Maps.MapControl.WPF.dll への参照を追加します。
-
コントロール追加MainWindow.xamlにMapコントロールを追加します。
<Window x:Class="BingMapSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wpf="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF" Title="MainWindow" Height="350" Width="525"> <Grid> <wpf:Map/> </Grid> </Window>
すると、デザインビューで地図が確認できます。
-
実行ここで、一度実行してみましょう。
地図が表示されましたね。「Invalid Credentials.~」というのはAPI Keyを入力してないがための文字列なのでとりあえずはOKです。-
API Key設定では、API Keyを設定して再び実行してみましょう
<wpf:Map CredentialsProvider="ここにAPI Keyを記述"/>
さて、再度実行!
あれ?地図が表示されない!?BingMapとの戦い(1回戦)
API Keyを設定したら、地図が表示されなくなってしまいました。仕方がないので、API Keyがない状態に戻して再度実行をしてみます。
やっぱり、地図は表示されません。なんだか不吉な感じがしてきました。ここで試したことを並べていきます。-
リビルド → ×ソリューションのクリーン → ×Visual Studio再起動 → ×Windows再起動 → ×OSの再起動までしてダメなら、割ともう打つ手はない感じです。
仕方がないので、別のマシンを取り出して同じことを試してみます。そうこうしているうちに、ある共通点が見つかりました。OSの1Userにつき、BingMapを表示するアプリケーションは一度目だけ地図が表示される。
これは、VisualStudioからの実行だけでなく、EXEを直接起動した場合も同じであるということは、ここで一つの仮説が思いつきますね!BingMapのDLLは、初回実行時にどこかにキャッシュ(?)を作成しており、キャッシュがある場合のみ地図表示に失敗する。(たぶんキャッシュロードのバグか何かで)比較的善意に解釈してみました。(悪意を持って2回目以降の地図表示を拒否してるとどうしようもないけど、Microsoftのコントロールはそういうことしないよねという前提)
.NETのモジュールがキャッシュファイルを作りそうな箇所はそれほど多くないので探してみます。思いつくのは↓あたりでしょうか-
C:\ProgramDataC:\Users\{各ユーザ}\AppDataで、結局下記にありました。
IsolatedStorage(分離ストレージ)に保存されていたようです。
【参考】(@IT)分離ストレージを活用するには?[C#、VB]
ファイルを削除することにより、2回目以降の起動時にも地図が表示されることが確認されました。対策検討会議
というわけで原因を見つけたものの、勝利!とはならないのです。
分離ストレージのファイルを削除すれば地図が表示されるとわかったものの、起動時に乱数で作ったっぽいフォルダ名の中にあるファイルを見つけて削除するというのは、まぁありえないわけです。
ならどうするか?
…
その前に、ひとつ疑問が。ヒントは2つ。-
2回目以降表示されないバグがあれば、さすがにネット検索にヒットしそう分離ストレージのファイル名末尾に「_JA-JP」とついているここから、導き出される答えは?ひょっとして、日本語環境だけ?日本語(+α)環境のみの不具合であれば、StackOverflowが引っかかることもないし、まぁありえる話かなと。
これは、英語OSでの実行を試してみるしかないですよね。でも英語OSなんて手元にない、なんてときは、Azureが便利ですよ。
で、結果はBingo。英語OSでは起きていなかったのです。ここまでくれば、少し光明が見えてきた感じがします。
.NETのDLLが実行時のCultureを判断して何かしてるということは-
Thread.CurrentThread.CurrentUICultureThread.CurrentThread.CurrentCultureのどちらかが絡んでいるはず。(というか、英語OSを試す前に気付きたかったです。)
で、両方とも試した結果、CurrentUICultureが関連していることが判明。アプリケーションの起動時にCultureを変更するコードを入れた結果、無事地図が表示されるようになったのでした。public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US"); } }
めでたし、めでたし。というわけにはいきませんね。CurrentUICultureがen-US固定になってしまいます。
さらに試行錯誤を加えた結果、次のことが判明しました。Mapコントロールをアプリケーションで最初に初期化する時にen-USだったらよいつまり、いったんCultureをen-USにして、Mapコントロールを初期化したのち、再びCultureを元に戻せばよさそうです。
結局、↓のようなコードを起動時に仕込むことになりました。public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var old = Thread.CurrentThread.CurrentUICulture; Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US"); var dummy = new Map(); Thread.CurrentThread.CurrentUICulture = old; } }
余談
設定するCultureは、en-US以外にもen-GBでもよいようです。その他のCultureもいくつか試してみましたが、ダメでした。
この2つのCultureに共通する点として、一つ気付いたのは、距離の単位です。milesになっているのがわかると思います。ちなみに、公式にヤード・ポンド法が使われている国は、英国と米国だけだそうです。
ひょっとするとこのことが関係しているのかもしれません。(裏は取ってないので単なる想像ですが。)後半へ続く
大きな落とし穴が開いていたBingMapControlですが、ひとまず表示できるようになりました。
ただ、戦いがこれで終わったわけではありません。というわけで、続きは次回。さらに大きな落とし穴が待ち受けています。(死2014年12月11日
Bindingを整理しよう
この記事は、XAML AdventCalendarの11日目の記事です。
皆さんがハードル上げまくってますが、ちょっと基本に立ち返り、XAMLと切っても切れない縁のBindingを整理してみたいと思います。
とはいえ、Bindingだけでも範囲がめっちゃ広いので、どこから値を持ってくるか?という点に絞ります。基本編
BindingはFrameworkElement.DataContextプロパティーに設定されたオブジェクトを参照します。
/// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = "Hello World!"; } }
<Window x:Class="BindingSamples.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Viewbox> <TextBlock Text="{Binding}" /> </Viewbox> </Grid> </Window>
DataContextに設定した文字が、TextBlockのTextとして表示されました。Path色々編
通常はDataContextに設定したオブジェクトを他のプロパティーに設定しなおすことはほぼなく、オブジェクトのプロパティーを参照することが多いです。
/// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = new { Text = "Hello World", Hoge = new { Fuga = "PIYO"}, Collection = new[] { "One", "Two", "Three" } }; } }
<Window x:Class="BindingSamples.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Viewbox> <StackPanel Orientation="Vertical"> <TextBlock Text="{Binding Text}" /> <TextBlock Text="{Binding Hoge.Fuga}" /> <TextBlock Text="{Binding Collection[1]}" /> </StackPanel> </Viewbox> </Grid> </Window>
Property名を指定することで、そのオブジェクトのPropertyの値を取得することができます。
また、「.」でプロパティー名を連結することで、階層的に値をたどっていくことも可能です。その他、[ ]を使いIndexerでのアクセスも可能です。
なお、Pathの指定は先頭に限り省略可能で、{Binding Text}と{Biding Path=Text}は同じ意味になります。
「/」を利用することにより、選択されている~といった指定も可能ですが、ここでは割愛。詳しくは↓を参照ください。
http://msdn.microsoft.com/ja-jp/library/ms742451(v=vs.110).aspx異なるソース編
ここまでのBindingはDataContextプロパティーのオブジェクトを参照していましたが、異なるソースを指定することもできます。
<Window x:Class="BindingSamples.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid x:Name="Grid"> <Viewbox> <StackPanel Orientation="Vertical"> <TextBlock Text="{Binding Path=MainWindow.Title, Source={x:Static Application.Current}}" /> <TextBlock Text="{Binding Path=ActualWidth, ElementName=Grid}" /> </StackPanel> </Viewbox> </Grid> </Window>
Sourceプロパティーを指定することで、他のオブジェクトを参照することができます。ここには、{x:Static} のほかに{StaticResource}などもよく使います。
また、ElementNameを指定すると、XAML上の他のオブジェクトへの参照を取得することができます。ここでは、最上位のGridが取得できます。異なるソース編 その2
自分自身から相対参照をもって値を取得することもできます。
<Window x:Class="BindingSamples.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid x:Name="Grid"> <Viewbox> <Grid> <StackPanel Orientation="Vertical"> <TextBlock Text="{Binding Path=FontFamily, RelativeSource={RelativeSource Self}}" /> <TextBlock Text="{Binding Path=Children.Count, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" /> <TextBlock Text="{Binding Path=Children.Count, RelativeSource={RelativeSource AncestorType={x:Type Grid} ,AncestorLevel=2}}" /> </StackPanel> </Grid> </Viewbox> <Grid/> </Grid> </Window>
Selfは文字通り、自分自身ですね。ここでは、TextBlock自身。
AncestorTypeは、FindAncestor(省略可能)モードの場合に使うキーワードで、XAMLをさかのぼって指定された型のオブジェクトを見つけ、参照します。これにAncestorLevelも追加すると見つけた2つ目の~みたいな指定ができるようになります。
あと、RelativeSourceでは特別な場合のみに使える指定があります。<Window x:Class="BindingSamples.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <ControlTemplate x:Key="Template" TargetType="{x:Type Button}"> <TextBlock Text="{Binding Path=Foreground,RelativeSource={RelativeSource TemplatedParent}}"/> </ControlTemplate> </Window.Resources> <Grid x:Name="Grid"> <Viewbox> <StackPanel Orientation="Vertical"> <Button Foreground="Red" Template="{StaticResource Template}" /> <Button Foreground="Blue" Template="{StaticResource Template}"/> </StackPanel> </Viewbox> </Grid> </Window>
まずは、Templateの内部でのみ使えるTemplateParent。文字通りTemplateを保持する親コントロールへの参照を取得します。
<Window x:Class="BindingSamples.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid x:Name="Grid"> <Viewbox> <ItemsControl ItemsSource="{Binding Collection}"> <ItemsControl.ItemTemplate> <DataTemplate> <Border BorderThickness="1" BorderBrush="Black"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <TextBlock Text="今"/> <TextBlock Grid.Row="1" Text="前"/> <TextBlock Grid.Column="1" Text="{Binding}" /> <TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding RelativeSource={RelativeSource PreviousData}}" /> </Grid> </Border> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Viewbox> </Grid> </Window>
あとは、ItemsControl系のDataTemplate内部でのみ使用できるPreviousData。これも文字通りで、ItemsSourceに指定されたCollectionのひとつ前の情報を取得できます。
# しかし、実際に使ったことはないのですよ。(どこで使うんだろう。。。)
というわけで、Bindingは結構柔軟にあちこちからデータを取得することができます。ぜひ活用してください。2014年12月5日
DataGridとDataTable(その2)【WPF編】
さて、前回の続き。
DataGridとDataTableは非常に相性がよいのですが、「AutoGenerateColumn=”True”」で使う際困った問題があります。
例えば、前回のソースコードを、次のように変更してみます。var table = new DataTable(); table.Columns.Add("Column1"); table.Columns.Add("Column.2"); table.Columns.Add("Column3"); table.Columns.Add("Column4"); for (int i = 0; i < 20; i++) { var row = table.NewRow(); for (int j = 0; j < table.Columns.Count; j++) { row[j] = "[" + (i + 1) + "," + (j + 1) + "]"; } table.Rows.Add(row); } this.DataContext = table;
違いは、Column名に「.」が入ったこと。そうすると
あら不思議。表示されなくなりました。
これは各方面で報告されている(例えば、ココとかココとか)通り で、WPFのBindingPathの解決の仕組みとColumn名がバッティングしているためです。回避方法として、提案されているのは
- Unicodeで似ている文字を使う
- BindingPathを”[ ]”で括る
です。Unicodeで似ている文字を使うのは論外として、”[ ]”で括るのは、このケースにおいては、一見うまくいっているように見えます。しかし、WPFのBindingPathの予約文字と被ったってことは、考慮しないといけないのは”.”だけではないことは容易に想像できますね。例えば、”[“とかがColumn名に含まれると非常にまずいわけです。(この場合は問答無用で例外が発生します。)
つまり、ClassのProperty名と違って使用不可文字のないDataColumn.ColumnNameを使用してBindingPathとすることはそもそも無理があるということになります。
ひとまず、自分の回避策を載せておきますが、どうするのが正解かはわかっていません。
var table = new DataTable(); table.Columns.Add(new DataColumn { ColumnName = "C" + table.Columns.Count, Caption = "Column1" }); table.Columns.Add(new DataColumn { ColumnName = "C" + table.Columns.Count, Caption = "Column.2" }); table.Columns.Add(new DataColumn { ColumnName = "C" + table.Columns.Count, Caption = "Column3" }); table.Columns.Add(new DataColumn { ColumnName = "C" + table.Columns.Count, Caption = "Column4" }); for (int i = 0; i < 20; i++) { var row = table.NewRow(); for (int j = 0; j < table.Columns.Count; j++) { row[j] = "[" + (i + 1) + "," + (j + 1) + "]"; } table.Rows.Add(row); } this.DataContext = table;
private void DataGrid_OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e) { var column = e.Column as DataGridBoundColumn; var view = (sender as DataGrid).ItemsSource as DataView; if (view != null && column != null) { var dataColumn = view.Table.Columns.OfType<DataColumn>().FirstOrDefault(c => c.ColumnName == e.PropertyName); if (dataColumn != null) { column.Header = dataColumn.Caption; } } }
ColumnNameは絶対にBindingPathの予約文字とかぶらないように、プログラム的に一意の文字列をふります。そして、Headerに表示したい文字列はCaptionを利用。その後、Column作成時にCaptionをHeaderに代入します。
ちなみに、DataTableはIndexで各ColumnとBindingができるので、そもそもAutoGenerateColumnでBindingを作成する際は、Indexでの解決にしてくれればよかったのに。。。と思ったりしないわけでもないのですが、どうなんでしょう。
# もしくは、BindingはColumnNameを使って、表示はCaptionを使うとか。
-
-
-
-