Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Emo's approach is a good one; I'd suggest a slightly different tack.</p> <p>First, set your existing program aside. You'll come back to it later.</p> <p>Next, implement a prototype WPF project. In this project, create a class that exposes <code>Letter</code>, <code>Row</code>, and <code>Column</code> properties, and another class that exposes a collection of these objects. Write a method that fills this collection with test data.</p> <p>In your main window, implement an <code>ItemsControl</code> to present this collection. This control needs four things:</p> <ol> <li><p>Its <code>ItemsPanel</code> must contain a template for the panel it's going to use to arrange the items it contains. In this case, you'll be using a <code>Grid</code> with rows and columns of a predefined size.</p></li> <li><p>Its <code>ItemContainerStyle</code> must contain setters that tell the <code>ContentPresenter</code> objects that the template generates which row and column of the grid they belong in.</p></li> <li><p>Its <code>ItemTemplate</code> must contain a template that tells it what controls it should put in the <code>ContentPresenter</code>s.</p></li> <li><p>Its <code>ItemsSource</code> must be bound to the collection of objects.</p></li> </ol> <p>A minimal version looks like this:</p> <pre><code>&lt;ItemsControl ItemsSource="{Binding}"&gt; &lt;ItemsControl.ItemsPanel&gt; &lt;ItemsPanelTemplate&gt; &lt;Grid&gt; &lt;Grid.RowDefinitions&gt; &lt;RowDefinition Height="10"/&gt; &lt;RowDefinition Height="10"/&gt; &lt;RowDefinition Height="10"/&gt; &lt;RowDefinition Height="10"/&gt; &lt;/Grid.RowDefinitions&gt; &lt;Grid.ColumnDefinitions&gt; &lt;ColumnDefinition Width="10"/&gt; &lt;ColumnDefinition Width="10"/&gt; &lt;ColumnDefinition Width="10"/&gt; &lt;ColumnDefinition Width="10"/&gt; &lt;/Grid.ColumnDefinitions&gt; &lt;/Grid&gt; &lt;/ItemsPanelTemplate&gt; &lt;/ItemsControl.ItemsPanel&gt; &lt;ItemsControl.ItemContainerStyle&gt; &lt;Style TargetType="ContentPresenter"&gt; &lt;Setter Property="Grid.Row" Value="{Binding Row}"/&gt; &lt;Setter Property="Grid.Column" Value="{Binding Column}"/&gt; &lt;/Style&gt; &lt;/ItemsControl.ItemContainerStyle&gt; &lt;ItemsControl.ItemTemplate&gt; &lt;DataTemplate TargetType="{x:Type MyClass}"&gt; &lt;TextBlock Text="{Binding Letter}"/&gt; &lt;/DataTemplate&gt; &lt;/ItemsControl.ItemTemplate&gt; &lt;/ItemsControl&gt; </code></pre> <p>Set the <code>DataContext</code> of the main window to a populated instance of your collection, and you should see it lay the letters out in a 4x4 grid.</p> <p>How this works: By setting <code>ItemsSource</code> to <code>{Binding}</code>, you're telling the <code>ItemsControl</code> to get its items from the <code>DataContext</code>. (Controls inherit their <code>DataContext</code> from their parent, so setting it on the window makes it available to the <code>ItemsControl</code>).</p> <p>When WPF renders an <code>ItemsControl</code>, it creates a panel using the <code>ItemsPanelTemplate</code> and populates it with items. To do this, it goes through the items in the <code>ItemsSource</code> and, for each, generates a <code>ContentPresenter</code> control. </p> <p>It populates the <code>Content</code> property of that control using the template found in the <code>ItemTemplate</code> property.</p> <p>It sets properties on the <code>ContentPresenter</code> using the style found in the <code>ItemContainerStyle</code> property. In this instance, the style sets the <code>Grid.Row</code> and <code>Grid.Column</code> attached properties, which tell the <code>Grid</code> where to put them when it draws them on the screen.</p> <p>So the actual objects that get created look like this:</p> <pre><code>&lt;Grid&gt; &lt;Grid.RowDefinitions&gt; &lt;RowDefinition Height="10"/&gt; &lt;RowDefinition Height="10"/&gt; &lt;RowDefinition Height="10"/&gt; &lt;RowDefinition Height="10"/&gt; &lt;/Grid.RowDefinitions&gt; &lt;Grid.ColumnDefinitions&gt; &lt;ColumnDefinition Width="10"/&gt; &lt;ColumnDefinition Width="10"/&gt; &lt;ColumnDefinition Width="10"/&gt; &lt;ColumnDefinition Width="10"/&gt; &lt;/Grid.ColumnDefinitions&gt; &lt;ContentPresenter Grid.Row="0" Grid.Column="0"&gt; &lt;ContentPresenter.Content&gt; &lt;TextBlock Text="A"/&gt; &lt;/ContentPresenter.Content&gt; &lt;/ContentPresenter&gt; &lt;ContentPresenter Grid.Row="1" Grid.Column="1"&gt; &lt;ContentPresenter.Content&gt; &lt;TextBlock Text="A"/&gt; &lt;/ContentPresenter.Content&gt; &lt;/ContentPresenter&gt; &lt;/Grid&gt; </code></pre> <p>(No actual XAML gets created, but the above XAML is a pretty good representation of the objects that do.)</p> <p>Once you have this working, you now have a bunch of relatively straightforward problems to solve:</p> <ol> <li><p>How do you make this look more like what you want it to look like? The answer to this is going to involve making a more elaborate <code>ItemTemplate</code>.</p></li> <li><p>How do you sync the collection that this is presenting up with the array in your application? This depends (a lot) on how your application is designed; one approach is wrapping the collection in a class, having the class create an instance of your back-end object model, and having the back-end object model raise an event every time its collection changes, so that the class containing the collection of objects that are being presented in the UI knows to create a new front-end object and add it to its collection.</p></li> <li><p>How does the user select a cell in the grid to put a tile into? The answer to this is probably going to involve creating objects for <em>all</em> the cells, not just the ones that contain letters, implementing a command to gets executed when the user clicks on the cell, and changing the <code>ItemTemplate</code> so that it can executes this command when the user clicks on it.</p></li> <li><p>If the contents of a cell change, how does the UI find out about it? This is going to require implementing <code>INotifyPropertyChanged</code> in your UI object class, and raising <code>PropertyChanged</code> when <code>Letter</code> changes.</p></li> </ol> <p>The most important thing about the approach I'm recommending here, if you haven't noticed, is that you can actually get the UI working almost completely independently of what you're doing in the back end. The UI and the back end are coupled together only in that first class you created, the one with the <code>Row</code>, <code>Column</code>, and <code>Letter</code> properties.</p> <p>This, by the way, is a pretty good example of the Model/View/ViewModel pattern that you've probably heard about if you're interested in WPF. The code you've written is the model. The window is the view. And that class with the <code>Row</code>, <code>Column</code>, and <code>Letter</code> properties is the view model.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload