[WPF] ComboBoxをItemsSourceとSelectedItemで扱う時の注意

SelectedItem で初期値を設定する場合には SelectedItem を先に設定してから ItemsSource を変更しないと初期値が設定されないようです。

味気ないので、雰囲気コード(実際には動かない)置いておきます。

こんなコンボボックスを作って

<ComboBox
  ItemsSource="{Binding Items}"
  SelectedItem="{Binding SelectedItem}"
  DisplayMemberPath="Name"
/>

ViewModel部分

public class ViewModel : INotifyPropertyChanged {
	public List<Item> Items { get; set; }	// 変更通知
	public Item SelectedItem { get; set; }	// 変更通知
	
	public void ChangeComboBox(){
		// これだとダメ
		//this.Items = new List<Item>(){ 
		//		new Item(){ Name = "1" },
		//		new Item(){ Name = "2" },
		//		new Item(){ Name = "3" },
		//	};
		//this.SelectedItem = this.Items.First();

		var items = new List<Item>(){
				new Item(){ Name = "1" },
				new Item(){ Name = "2" },
				new Item(){ Name = "3" },
			};
		this.SelectedItem = items.First();
		this.Items = items;
	}
}

public class Item {
	public string Name { get; set; }
	public int Value { get; set; }
}

ワンランク上のPOPUP コントロール

POPUP コントロール についてです。
よく 「ボタンを押す」→「ポップアップ出す」というデザインを使います。

ポップアップの動作的にはこんな感じになると思います。

  1. ボタンを押す。
  2. ポップアップを表示させる。
  3. ポップアップ以外の場所をクリックするとポップアップが閉じる。

ただね、

  1. ボタンを押す。
  2. ポップアップを表示させる。
  3. ボタンをクリックする。
  4. ボタンを押す時のMouseDownでポップアップが閉じる。
    MouseUPでポップアップが表示される。

ポップアップ閉じてくれえええええ


はい。
すっごい探しまして見つけました。
ToggleButton のNameを指定しないといけませんが、仕方ないでしょう。

<Grid>
	<ToggleButton Name="tbWithPopup" Width="30" Height="30"
			   IsChecked="{Binding ElementName=pUp, Path=IsOpen}">
		<ToggleButton.Style>
			<Style TargetType="ToggleButton">
				<Style.Triggers>
					<DataTrigger Binding="{Binding ElementName=pUp, Path=IsOpen}" Value="True">
						<Setter Property="IsHitTestVisible" Value="False" />
					</DataTrigger>
				 </Style.Triggers>
			</Style>
		</ToggleButton.Style>
		<Polygon Points="0,0 2,0, 1,1" Fill="Black" Stretch="Fill" />
	</ToggleButton>
		
	<Popup Name="pUp"
		   PlacementTarget="{Binding ElementName=tbWithPopup}"
		   AllowsTransparency="True" StaysOpen="False" Width="200">
		<RichTextBox IsReadOnly="True">
			<RichTextBox.Document>
				<FlowDocument>
					<Paragraph>
					   Checkout WPF Tat for more elegant 
					   solutions
					</Paragraph>
				</FlowDocument>
			</RichTextBox.Document>
		</RichTextBox>
	</Popup>
</Grid>

この人神様かな?
WPF TaT: ToggleButton with Popup

Visual Studio デザインモードの判定

Visual Studioのデザイナーすごい便利ですよね!
ただ、デザイン時には実行してほしくないコードが実行されてしまうときもあります。

そんなときはコードに実行中かどうかの判定を記述しましょう。
自分は以下のコードを使用しています。

if( System.ComponentModel.LicenseManager.UsageMode != System.ComponentModel.LicenseUsageMode.Runtime ) {
	//ここはデザイン時にしか通らない
}

他にも判定方法はあるようです。
.net - DesignMode with nested Controls - Stack Overflow

自作ライブラリを使う時に、Xamlのxmlnsで指定する名前空間について

自分でライブラリを作った時にXmlnsDefinitionを記述するとXamlで指定する名前空間に好きな文字を付けられます。
AssemblyInfo.cs にでも記述しておくとわかりやすいかな?

// [assembly: XmlnsDefinition("Xamlでの名前空間", "ライブラリのネームスペース")]

// 複数のnamespaceを同じ名前にしてもOK
[assembly: XmlnsDefinition("http://schemas.WpfApp1/", "WpfApp1")]
[assembly: XmlnsDefinition("http://schemas.WpfApp1/", "WpfApp1.View")]

使うときは指定した名前空間を使用します。

xmlns:wpfApp1="http://schemas.WpfApp1/"

名前空間の指定に決まりはなく、好きな名前が付けられるようです。

// これも行ける?
[assembly: XmlnsDefinition("俺のライブラリ", "WpfApp1")]


参考:
[WPF] WPF入門 ~XAML編 [名前空間]~
XAMLの基礎(1/2) - @IT
2009-09-27 - 当面C#と.NETな記録

[WinForm] NativeWindowでWinProcの処理を切り分け

WinFormの場合、WinProcからメッセージを受け取って処理を行うという処理をまとめたい場合にNativeWindowと便利になります。
といっても下記URLからの情報なのでこれ以上は無いです😭

マウスのホイールをコロコロした時にマウスオーバー(エンター?)しているコントロールのスクロールバーを動かしたい
C# マウスホバー状態のコントロールにホイールでスクロールさせる – Try&Error テクニカルブログ

[WPF] WebBrowserコントロール

WebBrowserコントロールを使用すると、アプリケーション内でブラウザを使用することができます。
が、これはMicrosoftのコントロールなのでInternet Explorerです。
しかも、互換性の問題とやらで標準ではIE7になります。
最新のIEを使用するためにはレジストリに値を追加する必要があります。

さらに、タッチ機能も無効になっているので有効にする必要があります。
さらに、ローカルのファイルを読み込むとセキュリティ警告が出るので、ローカルのファイル読み込みを有効にします。

void SetIEBrowserLatest() {
	var filename = Assembly.GetEntryAssembly().ManifestModule.Name;

	// WebbrowserのIEバージョンを指定
	using( var key = Registry.CurrentUser.CreateSubKey( @"SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION" ) ) {
		key.SetValue( filename, 11001, RegistryValueKind.DWord );
	}

	// ローカルのファイル読み込みを有効にする
	using( var key = Registry.CurrentUser.CreateSubKey( @"Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BLOCK_LMZ_SCRIPT" ) ) {
		key.SetValue( filename, 0, RegistryValueKind.DWord );
	}

	// タッチ機能を有効にする
	using( var key = Registry.CurrentUser.CreateSubKey( @"Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_NINPUT_LEGACYMODE" ) ) {
		key.SetValue( filename, 0, RegistryValueKind.DWord );
	}
}

タッチ機能を有効にしないと、ライブラリによってはJavascriptのイベントが走らずに動かないものがあります。
例えばこのライブラリ
image.py — Bokeh 0.12.14 documentation

参考:
scroll - C# WebBrowser PanningMode - Stack Overflow

また別の問題もあるようですが・・・
Windows 8のWebBrowserControlをタッチ対応にする - kkamegawa's weblog

WPFのデザインについて

WPFではView(デザイン)の変更がわりと簡単に行えるが、少し特殊なことをしたいときにはどうにもならない場合がある。

sygh.hatenadiary.jp

ControlTemplateを使った場合、指定したデザインに固定されてしまうため、Windowsのバージョンによって微妙に違うコントロールのデザインが違う部分が全く吸収できないのである。
さらに言えば、VisualStudioで「テンプレートの編集」→「コピーして編集」をした時のテンプレートが開発環境のOSに依存する。
Win7でこの作業を行うと、Win8で表示した時にWin7のデザインで表示されるわけです。


では、どうするかというと、上記サイトのようにテーマごとに地道にコードを書くしか無い感じです。

こういうところがWinFormに劣っている気がします。

VS Package での注意

Visual Studio Packageを作成します。

f:id:kitunechan:20170707110251p:plain

デンッ!

f:id:kitunechan:20170707104747p:plain


自動的に参照を追加してくれてDLLファイルがコピーされるのですが、どうもパスが長いようです。
保存しているソリューションのフォルダ場所によっては255文字を超えるためエラーになります。

自動的に追加される中でこの2つが原因です。

Microsoft.VisualStudio.Threading.dll
[ソリューションフォルダ]\packages\Microsoft.VisualStudio.Threading.15.0.240\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\Microsoft.VisualStudio.Threading.dll
Microsoft.VisualStudio.Validation.dll
[ソリューションフォルダ]\packages\Microsoft.VisualStudio.Validation.15.0.82\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10+MonoTouch10\Microsoft.VisualStudio.Validation.dll

パッケージの中身だけで162文字あるんだけど!!!
パス長すぎ!!!

Visual Studioで開く の削除方法

Visual Studio 2017をインストールするとフォルダの右クリックメニューに「Visual Studioで開く」が追加されます。
削除するには以下のレジストリを削除します。

Windows Registry Editor Version 5.00
[-HKEY_CLASSES_ROOT\Directory\Background\shell\AnyCode]
[-HKEY_CLASSES_ROOT\Directory\shell\AnyCode]

コマンドプロンプトを管理者権限で起動して以下のコマンドを打てばおっけー

reg delete HKEY_CLASSES_ROOT\Directory\Background\shell\AnyCode /f
reg delete HKEY_CLASSES_ROOT\Directory\shell\AnyCode /f

http://dentnt.windowsfaq.ru/?p=5837

TextBoxのキャレットが消える問題

TextBoxをScaleTransformで縮小するとキャレット(カーソルの場所の縦棒|)がたまに消えます。

まあ、キャレットの1ドットも一緒に縮小されて消えちゃうんだろうなーと思いますが。。。

解決方法はScaleTransformを使わないことです。
もしも縮小していたらInverseとかで100%に戻せばバグりません。
拡大縮小についてはFontSizeでなんとかしろということでした。

c# - WPF TextBox Caret Disappearing - Stack Overflow

GitHub Microsoft

GitHubMicrosoft公式のリポジトリがあります。
Microsoft · GitHub

いろんなサンプルがあるので参考になりそうです。
GitHub - Microsoft/Windows-classic-samples: This repo contains samples that demonstrate the API used in Windows classic desktop applications.
GitHub - Microsoft/WPF-Samples: Repository for WPF related samples
GitHub - Microsoft/Windows-universal-samples: API samples for the Universal Windows Platform.

StaticPropertyの変更通知

かねがねStaticなプロパティの変更通知ができないか困っておりました。

ありました。
StaticPropertyChanged

.Net 4.5からだそうで、英語読めないので見つけられませんでした。


よくわからないのですが、StaticPropertyChangedイベントを呼び出せばいいみたいです。
staticなのでinterfaceとか使えないので勝手にイベントを追加するだけのようです。

public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;

こういうことだー

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ねーむすぺーす.Models {
	public class TestClass {
		static public string CurrentValue {
			get { return _CurrentValue; }
			set {
				if( _CurrentValue != value ) {
					_CurrentValue = value;
					NotifyStaticPropertyChanged();
				}
			}
		}
		static private string _CurrentValue = "てすと";

		public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
		private static void NotifyStaticPropertyChanged( [CallerMemberName] string propertyName = "" ) {
			if( StaticPropertyChanged != null )
				StaticPropertyChanged( null, new PropertyChangedEventArgs( propertyName ) );
		}
	}
}

こう使います。

<!-- m に対してネームスペースを追加して -->
<Window xmlns:m="clr-namespace:ねーむすぺーす.Models">

<!-- Path は省略できません Staticな部分の()カッコ重要 -->
<TextBlock Text="{Binding Path=(m:TestClass.CurrentValue)}" />

Staticのクラス内を参照する場合はこんな感じになります。
※プロパティ名とか適当です

<TextBlock Text="{Binding Path=(m:TestClass.CurrentModel).Value}"/>

追記:
()カッコで簡潔に記述できるのですが、Xamlデザイナーがエラー出まくるのでXamlの方でリソースを作成したほうが良さそうです。

<Window.Resources>
	<m:TestClassx:Key="TestClass" />
</Window.Resources>

<TextBlock Text="{Binding CurrentValue, Source={StaticResource TestClass}}" />

<!-- クラス(TestClass)がstatic(public static class TestClass {...})ならリソースの作成は必要ないです。-->
<TextBlock Text="{Binding CurrentValue, Source={x:Static m:TestClass}}" />


詳しくは参考URLへどうぞ(丸投げ)

参考:
WPF 4.5 での バインディング強化
WPF 4.5: Binding and change notification for static properties - Pete Brown's 10rem.net
WPF 4.5 – Part 9 : binding to static properties UPDATED | Jonathan ANTOINE's thoughts on Windows development

ItemsSource に List<String> を指定しても編集できませんので

List<String>の中身を軽く編集しようと思って、DataGridやListBoxのItemsSourceに指定したら動かないっ・・・

こんなListを

public List<String> Items {
	get { return _Items; }
	set { _Items = value; }
}
private List<string> _Items = new List<String> { "One", "Two", "Three" };

簡単編集だ!

<!-- 編集するとエラー (両方向のバインドには、Path または XPath が必要です。) -->
<DataGrid ItemsSource="{Binding Items}">
	<DataGrid.Columns>
		<DataGridTextColumn Binding="{Binding .}" />
	</DataGrid.Columns>
</DataGrid>

<!-- 編集しても反映されない -->
<ListBox ItemsSource="{Binding Items}">
	<ListBox.ItemTemplate>
		<DataTemplate>
			<TextBox Text="{Binding .}" />
		</DataTemplate>
	</ListBox.ItemTemplate>
</ListBox>

動かない!
Path にドット.つけてるのに😭

はい、ドット.とか無意味なんですね。
バインドしている対象が値型(文字、数値など)では参照を変更できないので編集はできません!
諦めましょう。

何かしらのクラスに内包してやるしか無いみたいです。
こんな感じにして

public static List<String> Items = new List<String> { "One", "Two", "Three" };

public List<BindingProxy<string>> ItemsProxy {
	set { _ItemsProxy = value; }
	get { return _ItemsProxy; }
}
private List<BindingProxy<string>> _ItemsProxy = new List<BindingProxy<string>>( Items.Select( x => new BindingProxy<string>( x ) ) );

public class BindingProxy<T> {
	public BindingProxy() {}
	public BindingProxy( T value ) {
		this.Value = value;
	}
	public T Value { get; set; }
}

今度こそ簡単編集だ!

<DataGrid ItemsSource="{Binding ItemsProxy}">
	<DataGrid.Columns>
		<DataGridTextColumn Binding="{Binding Value}" />
	</DataGrid.Columns>
</DataGrid>


<ListBox ItemsSource="{Binding ItemsProxy}">
	<ListBox.ItemTemplate>
		<DataTemplate>
			<TextBox Text="{Binding Value}" />
		</DataTemplate>
	</ListBox.ItemTemplate>
</ListBox>

あとでItemsProxyをItemsに戻してデータ編集完了です。

参考:
WPF: Bind DataGrid to List<String> - Stack Overflow

Bindign用のConverterの書き方

普通の書き方

だいたいConverterの使い方として説明されるのがこんな感じだと思います。

namespace Namespace.Converters {
	public class CustomConverter: IValueConverter {
		public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) {	
			// ここに処理を書く
			throw new NotImplementedException();
		}

		public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture ) {
			// ここに処理を書く
			throw new NotImplementedException();
		}
	}
}

 

Namespaceとかは適当
<Window xmlns:convert="clr-namespace:Namespace.Converters">
    <Window.Resources>
        <convert:CustomConverter x:Key="CustomConverter"/>
    </Window.Resources>

    <TextBlock Text="{Binding value, Converter={StaticResource CustomConverter}}"/>
</Window>

はい、Resourcesにいちいち入れておくのめんどくさいですね。

直接書く

直接書いてやりましょう。

<Window xmlns:convert="clr-namespace:Namespace.Converters">
	<TextBlock>
		<TextBlock.Text>
			<Binding Path="value">
				<Binding.Converter>
					<convert:CustomConverter/>
				</Binding.Converter>
			</Binding>
		</TextBlock.Text>
	</TextBlock>
</Window>

XAML長すぎでしょ・・・


Staticインスタンスを作っておく

XAML長すぎ問題がなんとかならないかと探していたところ、”Static作っとけばいいじゃん”みたいな書き込みを見つけました。

public class CustomConverter: IValueConverter {

	// Staticのインスタンスを作っちゃう
	private static CustomConverter _instance = new CustomConverter();
	public static CustomConverter Instance { get { return _instance; } }

	public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) {
		// ここに処理を書く
		throw new NotImplementedException();
	}


	public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture ) {
		// ここに処理を書く
		throw new NotImplementedException();
	}
}

呼び出すときはこんな感じ

<Window xmlns:convert="clr-namespace:Namespace.Converters">
    <TextBlock Text="{Binding value, Converter={x:Static convert:CustomConverter.Instance}}"/>
</Window>

便利になりました。
ただ、Template内で使用するとデザイナーがエラー表示をだします。(VS2013)
(リソースを解決できません みたいなエラー)
コンパイルもできるし、実行も問題ないんですけどね・・・


MarkupExtensionを使う

さらに、MarkupExtension を継承しておけばStaticすらいらなくなります。

public class CustomConverter: MarkupExtension, IValueConverter {

	public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) {
		// ここに処理を書く
		throw new NotImplementedException();
	}

	public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture ) {
		// ここに処理を書く
		throw new NotImplementedException();
	}

	public override object ProvideValue( IServiceProvider serviceProvider ) {
		return this;
	}
}
<Window xmlns:convert="clr-namespace:Namespace.Converters">
    <TextBlock Text="{Binding value, Converter={convert:CustomConverter}}"/>
</Window>

😃😃😃😃😃😃😃😃😃

こういうテクニックはなかなか見つからないですね。

参考:
WPF:Converterを使うとき、xamlのResource内で宣言をしたくないときの書き方 - RunningCSharp
WPF: How to use converters without creating static resources

[C#][WPF]マークアップ拡張の作り方

続きを読む

Visual Studio 不具合一覧

Visual Studio 不具合一覧

ブレークポイントが別のプロジェクトでも有効となる

  1. 同一ソリューション内の複数のプロジェクトが存在する。
  2. 複数のプロジェクトに同じファイル名があり、1つのファイルでブレークポイントを置く
  3. 同名のファイル全てに対してブレークポイントが発生する

Visual Stuido 2015 - Update3 → 未解決

解決方法:同じファイル名にしないようにしましょう・・・😭😭😭