質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

XAML

XAML(Extensible Application Markup Language)はWPF、Silverlight、Windows PhoneそしてWindows Store appsでユーザーインターフェースを定義するために使われるXML言語です。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

Q&A

解決済

2回答

3118閲覧

WPFで2次元配列にセットした画像を表示したい

退会済みユーザー

退会済みユーザー

総合スコア0

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

XAML

XAML(Extensible Application Markup Language)はWPF、Silverlight、Windows PhoneそしてWindows Store appsでユーザーインターフェースを定義するために使われるXML言語です。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

0グッド

0クリップ

投稿2020/10/07 07:03

編集2020/10/07 08:23

環境

VisualStudio2017(WPF)

実現したいこと

イメージ説明

やりたいのは上画像の6*5のすべての目に5種類のピースの画像をランダムに並べることです。
現状はSTARTボタンをクリックするとピースが並べられるようにしているつもりですが、ボタンを押しても何も表示されない状態です。

以下、ソースと現状の詳細を記載します。
インデクサのBindingに失敗していそうだと思っているのですが、どこが悪いでしょうか?

開発経験はなくC#を触り始めたばかりの初心者のため、かなりお見苦しいコードになっていると思いますが、ご回答のほどよろしくお願いします。

###現状のソース

xaml

1<Window x:Class="Pazzle.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:PazDra" 7 mc:Ignorable="d" 8 Title="MainWindow" Height="700" Width="500"> 9 <Grid> 10 <Button x:Name="btn_GemGenerate" Content="START" HorizontalAlignment="Center" Margin="0,200,0,0" VerticalAlignment="Top" Width="100" Height="50" FontSize="20" Click="button_Click_START"/> 11 <Grid x:Name="BoardGrid" HorizontalAlignment="Center" Margin="0,0,0.0,0" VerticalAlignment="Bottom" ShowGridLines="True" Height="300" Width="350" Grid.ColumnSpan="4" Opacity="0.985"> 12 <Grid.ColumnDefinitions> 13 <ColumnDefinition Width="*"/> 14         <!--6列。長いので以下略--> 15 </Grid.ColumnDefinitions> 16 <Grid.RowDefinitions> 17 <RowDefinition Height="*"/> 18         <!--5行。長いので以下略--> 19 </Grid.RowDefinitions> 20 <!--インデクサPeace[x][y]をImageのSourceにBindingし、下のC#のソースでモデル上の盤面に配置した各要素を表示する。--> 21 <Image x:Name="GridCell0_0" Source="{Binding Path=Peace[0][0]}" Margin="1" Grid.Row="0" Grid.Column="1"/> 22 <Image x:Name="GridCell1_0" Source="{Binding Path=Peace[1][0]}" Margin="1" Grid.Row="0" Grid.Column="1"/> 23 <Image x:Name="GridCell2_0" Source="{Binding Path=Peace[2][0]}" Margin="1" Grid.Row="0" Grid.Column="2"/> 24 <Image x:Name="GridCell3_0" Source="{Binding Path=Peace[3][0]}" Margin="1" Grid.Row="0" Grid.Column="3"/> 25 <!--盤上のすべての目に画像をBinding。配列の記法はPeace[,]のほうが正しいかも。現状はどちらの記法でも動かない--> 26 <!--長いので以下略--> 27 </Grid> 28 </Grid> 29</Window>

c#

1/// MainWindow.xaml の相互作用ロジック 2 3namespace Pazzle 4{ 5 public partial class MainWindow : Window 6 { 7 public MainWindow() => InitializeComponent(); 8 9 //STARTボタンをクリックすると盤上にピースが配置される。 10 private void button_Click_START(object sender, RoutedEventArgs e) 11 { 12 Board board = new Board(); 13 GridCell0_0.DataContext = board; 14 } 15 } 16} 17

C#

1//2次元配列の添え字でアクセスして盤上のピースを配置、取得するインデクサ 2namespace Pazzle 3{ 4 class Peaces 5 { 6 public const int WIDTH = 6; 7 public const int HEIGHT = 5; 8 string[,] PeaceImg = new string[HEIGHT, WIDTH]; 9 public string this[int X, int Y] 10 { 11 set 12 { 13 this.PeaceImg[X,Y] =value; 14 } 15 get 16 { 17 return this.PeaceImg[X,Y]; 18 } 19 } 20 } 21}

c#

1//盤面を表すモデル 2 3namespace Pazzle 4{ 5 class Board 6 { 7   //盤面は6*5 8 public const int WIDTH = 6; 9 public const int HEIGHT = 5; 10 11 //ピースの画像ファイルへのパス 12 public const string PEACE_A = @"C:\hoge\hoge"; 13 public const string PEACE_B = @"C:\hoge\fuga"; 14 public const string PEACE_C = @"C:\fuga\hoge"; 15 public const string PEACE_D = @"C:\fuga\fuga"; 16 public const string PEACE_E = @"C:\piyo\piyo"; 17 18 //ピースは5種類 19 public static readonly string[] PeaceType = new string[] 20 { 21 PEACE_A,PEACE_B,PEACE_C,PEACE_D,PEACE_E 22 }; 23 24 int seed; //乱数生成用のシード値 25 public Peaces peace = new Peaces();//盤上の目に配置するピースを表すインデクサ 26 public Board() 27 { 28 for (int i = 0; i < HEIGHT; i++) 29 { 30 for (int j = 0; j < WIDTH; j++) 31 { 32 seed = System.Environment.TickCount; 33 Random rnd = new System.Random(seed++); 34 peace[i, j] = PeaceType[rnd.Next(4)]; 35            //盤上のすべての目にランダムに4種のピースを配置 36 } 37 } 38 } 39 } 40}

###現状

######実現できていること

  • STARTボタンを押下するとPeace[x][y]のすべての要素に画像のパスは入ります。

######実現できていないこと

  • Peace[x][y]に入っているはずの画像がView上に表示できません。

 xamlでimage要素の代わりにTextBlockを使って、画像ではなくパスの文字列を表示することも試しましたが、文字列でも表示できませんでした。

  • ループ内でシード値をインクリメントして変えながら乱数を生成しているのですが、すべて同じ種類のピースになってしまいます。

(デバッグモードでゆっくりとステップ実行すると違う種類になるのですが。。。)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

public Peaces peace = new Peaces();
は、
public Peaces Peace { get; } = new Peaces();
ですね。
プロパティでないとバインドできないのと、xamlでは大文字になっています。

Source="{Binding Path=Peace[0][0]}"
は、
Source="{Binding Path=Peace[0\,0]}"
です。
コードでは四角い配列([,])になっています。
そのためPeace[0,0]となりますが、カンマがエラーになるので\でエスケープします。

NaK310さんの制作物で使えるかどうかはわかりませんが、ItemsControlUniformGridでのxaml短縮化例も付けました。

めんどくさいので2*2にしています^^;

xml

1<Window 2 x:Class="Questions296474.MainWindow" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 Width="500" 6 Height="500"> 7 <StackPanel> 8 <Button 9 HorizontalAlignment="Center" 10 Click="button_Click_START" 11 Content="START" 12 FontSize="20" /> 13 <Grid 14 Width="120" 15 Height="120" 16 ShowGridLines="True"> 17 <Grid.ColumnDefinitions> 18 <ColumnDefinition /> 19 <ColumnDefinition /> 20 </Grid.ColumnDefinitions> 21 <Grid.RowDefinitions> 22 <RowDefinition /> 23 <RowDefinition /> 24 </Grid.RowDefinitions> 25 <Image Source="{Binding Path=Peace[0\,0]}" /> 26 <Image Grid.Column="1" Source="{Binding Path=Peace[1\,0]}" /> 27 <Image Grid.Row="1" Source="{Binding Path=Peace[0\,1]}" /> 28 <Image 29 Grid.Row="1" 30 Grid.Column="1" 31 Source="{Binding Path=Peace[1\,1]}" /> 32 </Grid> 33 34 <!-- ItemsControlでやった場合 XY反転しているので注意 --> 35 <ItemsControl 36 Width="120" 37 Height="120" 38 ItemsSource="{Binding Peace}"> 39 <ItemsControl.ItemsPanel> 40 <ItemsPanelTemplate> 41 <UniformGrid Columns="2" Rows="2" /> 42 </ItemsPanelTemplate> 43 </ItemsControl.ItemsPanel> 44 <ItemsControl.ItemTemplate> 45 <DataTemplate> 46 <Image Source="{Binding}" /> 47 </DataTemplate> 48 </ItemsControl.ItemTemplate> 49 </ItemsControl> 50 </StackPanel> 51</Window>

cs

1using System; 2using System.Collections; 3using System.Collections.Generic; 4using System.Windows; 5 6namespace Questions296474 7{ 8 internal class Board 9 { 10 public const int WIDTH = 2; 11 public const int HEIGHT = 2; 12 public const string PEACE_A = @"https://teratail-v2.storage.googleapis.com/uploads/avatars/u13/132786/KnkDDC5A_thumbnail.jpg"; 13 public const string PEACE_B = @"https://teratail-v2.storage.googleapis.com/uploads/avatars/u14/143281/4935fb53e9921aff_thumbnail.jpg"; 14 15 public static readonly string[] PeaceType = new string[] { PEACE_A, PEACE_B }; 16 17 // プロパティでないとバインドできません 18 public Peaces Peace { get; } = new Peaces(); 19 20 // 1回作ればいいです 21 private static readonly Random rnd = new Random(); 22 23 public Board() 24 { 25 for(var i = 0; i < HEIGHT; i++) 26 { 27 for(var j = 0; j < WIDTH; j++) 28 { 29 Peace[i, j] = PeaceType[rnd.Next(PeaceType.Length)]; 30 } 31 } 32 } 33 } 34 35 internal class Peaces : IEnumerable<string> 36 { 37 public const int WIDTH = 2; 38 public const int HEIGHT = 2; 39 private readonly string[,] PeaceImg = new string[HEIGHT, WIDTH]; 40 41 public string this[int X, int Y] 42 { 43 set => PeaceImg[X, Y] = value; 44 get => PeaceImg[X, Y]; 45 } 46 47 // ItemsControlで使えるようIEnumerable実装 48 public IEnumerator<string> GetEnumerator() 49 { 50 foreach(var item in PeaceImg) yield return item; 51 } 52 IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 53 } 54 55 public partial class MainWindow : Window 56 { 57 public MainWindow() => InitializeComponent(); 58 59 private void button_Click_START(object sender, RoutedEventArgs e) 60 => DataContext = new Board(); 61 } 62}

イメージ説明

投稿2020/10/07 08:35

編集2023/07/23 07:27
TN8001

総合スコア9862

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/10/07 09:00 編集

ありがとうございます。ここまで書いていただけるとはおもわず、感動しました。。。 ちょっと理解するのに時間がかかりそうなので、明日中に改めて確認します。 (プロパティでないとバインドできない、配列名にエスケープシーケンスが必要という点が大きなミスのようですね)
退会済みユーザー

退会済みユーザー

2020/10/08 00:22

表示できました、ありがとうございます! Peaceをプロパティにするのと、xaml上の配列を[x\,y]の形式に書き換えるのが肝だったようですね。 xamlは今のままではさすがに気持ち悪いので、教えていただいたItemsControlとUniformGridについて勉強したうえで改善しようと思います。 本当にありがとうございました。
guest

0

こんにちは。
いくつか書き方に問題があったので、順に説明していきます。

##DataContextがImage毎に設定されている
DataContextはWindowやGridなど入れ子における上位のコントロールにも設定出来ます。
上位のコントロールにDataContextを設定することで、各Imageコントロールに「〜_0_0」などとNameプロパティを設定していく必要が無くなります。
小規模なアプリではWindowにのみDataContextを設定することが多い気がするので、WindowにDataContextを設定してはいかがでしょうか。

##DataContextがINotifyPropertyChangedを実装していない
DataContextに設定するクラスは、その中のプロパティが変更されたことをWindowなどに通知する為、INotifyPropertyChangedインターフェイスを実装している必要があります。
実装方法はご自身で調べてみて下さい。
あるいは、PrismやMvvmLightなどのINotifyPropertyChanged実装を補助する外部フレームワークを利用するのも手です。

##(追記)参考:MVVMに沿った設計
WPFアプリはMVVMという設計思想に沿って設計するのが一般的となっています。
MVVMはModel-View-ViewModelの略で、

  • ウィンドウやユーザーコントロールなど、画面への表示に直接関与するクラスをView
  • ViewにBindingするプロパティを持つクラスをViewModel
  • ロジックなどを持つクラスをModel

というように、クラスのメンバの役割によってクラスを分離し、コードを読みやすくすることが出来ます。

今回の質問のコードに照らし合わせると、ViewになるのはMainWindowクラスです。
また、ViewModelに相当するクラスを新たに作り、その中にPeaceプロパティとボタンクリック時のメソッドを含ませるのが良いと思います。

私がPrismerなのでPrismを利用して書きますが、ViewModelクラスは以下のような感じになります。
実行環境が手元に無いので、もしかしたら動かないかもしれません。動かなかった場合はごめんなさい。

C#

1//using Prism.Mvvm; 2//using Prism.Commands; 3 4// BindableBaseがINotifyPropertyChangedを実装してくれています。 5// MainWindowなどのDataContextにこのクラスのインスタンスを指定して下さい。 6public class MainWindowViewModel : BindableBase 7{ 8 public Peaces Peace 9 { 10 get => board.peace; 11 set => SetProperty(ref board.peace, value); 12 } 13 14 // MVVMでは、ボタンクリック時のメソッドなどもVMに書きます。 15 // Xaml内のButtonの「Clicked="〜"」を「Command="{Binding ButtonClicked}"」に書きかえておいて下さい。 16 public DelegateCommand ButtonClicked { get; set; } 17 18 Board board = null; 19 20 public MainWindowViewModel() 21 { 22 ButtonClicked = new DelegateCommand(() => board = new Board()); 23 } 24}

##その他気になったこと

  • Bindingの書き方がジャグ配列の形式(配列名[x][y])になっていますが、Peace[x,y]でないと正しく取得出来ません。
  • PeaceではなくPieceではないでしょうか?

投稿2020/10/07 07:34

編集2020/10/08 08:39
Automatic9045

総合スコア313

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/10/07 07:59

早速のご回答ありがとうございます。 とりあえずやり方を調べてみてINotifyPropertyChangedインターフェイスを実装して試します。 >Bindingの書き方がジャグ配列の形式(配列名[x][y])になっているのは大丈夫ですか? これは、「配列名[,]」の形式でなくて大丈夫か、ということでしょうか。 どちらの書き方が正しいのかわからなかったので、両方やってみていたのですが、現状ではどちらにせよ動かないので判断を保留している感じです。 追記しておきます。
Automatic9045

2020/10/07 08:50 編集

> これは、「配列名[,]」の形式でなくて大丈夫か、ということでしょうか。 そういうことです。言葉足らずですみません… これについてはTN8001さんが詳しく回答して下さっています。 折角なので、WPFでよく採用されるMVVMという設計思想について追記しておきました。 よければ参考にして下さい。
退会済みユーザー

退会済みユーザー

2020/10/07 08:57

ありがとうございます。 配列名については、まさかエスケープシーケンスが必要だったとは、盲点でした。 MVVMについての追記もありがとうございます! Viewはxamlかと思っていたのですが、勘違いしていたようですね。勉強してみます。
Automatic9045

2020/10/07 09:05

> Viewはxamlかと思っていたのですが 確かにxamlはViewの一部ではありますが、正確には - ViewはMainWindowクラス - xamlコードはInitializeComponentメソッド内でC#コードにパースされている→xamlコードはMainWindowクラスの一部 ということになります。 この辺りの説明はネット上には本当に少ないんですよね。私も過去相当苦戦した経験があります。 /* エスケープシーケンスについて教えて下さったのはTN8001さんです。2次元配列をBindingしたことが無かったので、私も勉強させて頂きました。 */
退会済みユーザー

退会済みユーザー

2020/10/08 00:25

xamlコードはMainWindowクラス(=View)の一部として扱われることでViewとして機能しているということですね。 自分で調べてもわからなかったことだと思います。 MVVMにしたがってクラス設計を見直してみますね。 本当にありがとうございました。
Automatic9045

2020/10/08 08:40

ひとまず解決したようで良かったです。 ただ、1つとても気になっていることがあります……peaceではなくpieceではありませんか…?
退会済みユーザー

退会済みユーザー

2020/10/09 05:09

誤字ですね。お恥ずかしい。。 ただ、teratailに質問する用に書き直したコードなので、本来の制作物には誤字はありません。 ご指摘ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問