Generating grid dynamically in MVVM pattern - wpf

I am writing a WPF application where where i need to display custom file iformation which consists of field name & its value. I generate a grid rumtime with label & textboxes. I display the field name in label & field value in textbox(i want it to be editable). & each time file selection changes, number of field change & so the grid columns & rows. Right now I am generating this grid in code behind . Is there any way i can do it in XAml with view model.

This is pretty easy to do with an ItemsControl. If you ViewModel exposes a list of metadata objects, say a class like this:
public class FileMetaData : INotifyPropertyChanged
{
private string name;
private string value;
public event PropertyChangedEventHandler PropertyChanged = (o, e) => { };
public string Name
{
get { return name; }
set
{
name = value;
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
public string Value
{
get { return value; }
set
{
this.value = value;
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
}
Then, your ViewModel would expose it as an ObservableCollection (so WPF knows when new items are added or removed):
public class MyViewModel
{
...
public ObservableCollection<FileMetaData> Files { get; private set; }
...
}
Then, your view would use an ItemsControl with an ItemTemplate to display it:
<ItemsControl ItemsSource="{Binding Files}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="one" />
<ColumnDefinition Width="Auto" SharedSizeGroup="two" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" />
<TextBox Grid.Column="1" Text="{Binding Value}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Note that I'm setting Grid.IsSharedSizeScope to true on the ItemsControl, so the columns will align. If you have a lot of data, you'll probably want to wrap this in a ScrollViewer (or better retemplate the ItemsControl to have one).

I'm not sure why you're creating this grid at runtime. You should look into using a standard presentation method such as a <ListBox> with a custom item template. Always look to use declaritive definition of your UI (within the XAML) instead of the codebehind.
I've got a blog post on creating a checked listbox that shows some of the details, but you should be able to find other good examples out there as well.

Related

WPF MVVM passing data between viewmodel but property not updating

I'm making a Window to manage the users who using laptop. I have the window named "LaptopWindow" which contain a TextBox to display the user id of the one using it. I have made a button to open new UserControl named "FindEmployeeUC" to find the "EmpID" by select the row in DataGrid of UserControl and pass it back to the TextBox in "LaptopWindow".
I got the selected row of the DataGrid and use the property name "SelectedUA" to hold it inside the view model "UserAccountViewModel".
When OnPropertyChanged event fire I call the instance of "LaptopManagementViewModel" (this view model is bound with "LaptopWindow") and set the EmpID to the TextBox in "LaptopWindow" by the property named "ReceiverID"
The property "ReceiverID" got value but the UI of "LaptopWindow" didn't get update.
I tried to use Delegate, Singleton pattern, It had the same result.
Here is some code to explain more what I'm facing
The "LaptopWindow" xaml:
<StackPanel Grid.Row="2" Grid.Column="1" Style="{StaticResource inputControl}">
<TextBlock Text="Người nhận"/>
<TextBox Name="txtReceiver" Text="{Binding ReceiverID,Source={StaticResource vmLaptopManagement}}" Margin="0,0,30,0"/>
</StackPanel>
<!--Button open FindEmpUC -->
<Button Grid.Row="2" Grid.Column="1" Width="30" Height="29" HorizontalAlignment="Right" VerticalAlignment="Bottom" Background="Transparent" Margin="0,4,4,4" Command="{Binding CmdFindEmp}">
<Image Source="/imgs/find-48.png" Stretch="Uniform" />
</Button>
The "LaptopManagementViewModel":
//the userAccountVM
UserAccountViewModel userAccountVM;
//the constructor
public LaptopManagementViewModel(UserAccountViewModel userAccountVM)
{
LstDVUS = LaptopManagementBLL.Instance.GetDVUsageStatuses();
LstLaptop = LaptopManagementBLL.Instance.GetLaptopsInfo();
this.userAccountVM = userAccountVM;
ReceiverID = this.userAccountVM.SelectedUA.EmpID;
}
//the ReceiverID property
string receiverID;
public string ReceiverID
{
get { return receiverID; }
set
{
receiverID = value;
OnPropertyChanged("ReceiverID");
}
}
//function open FindEmployeeUC
private void FindEmployee(object obj)
{
//show findEmployee UC
Window wd = new Window()
{
Content = new FindEmployeeUC(),
};
wd.ShowDialog();
}
The "FindEmployeeUC" xaml:
<DataGrid Grid.Row="1" ItemsSource="{Binding LstUA}" CanUserAddRows="False" SelectedItem="{Binding SelectedUA,Mode=TwoWay}" AutoGenerateColumns="False" ColumnWidth="*" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}"></DataGridTextColumn>
<DataGridTextColumn Header="EmpID" Binding="{Binding EmpID}"></DataGridTextColumn>
<DataGridTextColumn Header="EmpName" Binding="{Binding EmpName}"></DataGridTextColumn>
<DataGridTextColumn Header="Position" Binding="{Binding Position}"></DataGridTextColumn>
<DataGridTextColumn Header="LineGroup" Binding="{Binding LineGroup}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
The "UserAccountViewModel":
//The property "SelectedUA"
UserAccountModel selectedUA;
public UserAccountModel SelectedUA
{
get { return selectedUA; }
set
{
if(selectedUA!=value)
{
selectedUA = value;
LaptopManagementViewModel laptopVM = new LaptopManagementViewModel(this);
OnPropertyChanged("SelectedUA");
}
}
}
I expect to get the EmpID for the TextBox in "LaptopWindow". I attach a picture for more detail:
Thanks in advance!
In your OnPropertyChanged event invocator you are always creating a new instance of UserAccountViewModel. This instance is never referenced in your XAML code, therefore your view can't see this new instances.
Since view model have a state you typically use a single instance for a binding target.
I removed the parameterized constructor to enable the instantiation in XAML (the instance is assigned to the UserAccountVM property from XAML) and also removed the reference to LaptopManagementViewModel from UserAccountViewModel. I created the view model instances and added them to the ResourceDictionary of App.xaml.
I also added a PropertyChanged event handler to the LaptopManagementViewModel to listen for changes of UserAccountViewModel.SelectedUA.
It is also highly recommended to avoid string literals. Instead of calling OnPropertyChanged("MyProperty") you should use the free compiler support by applying nameof(): OnPropertyChanged(nameof(MyClass.MyProperty)). I replaced the corresponding code. You now get rid of typos and get full support of compiler checks and refactoring tools (e.g. renaming).
Also stay away from Singletons. They smell strong.
Last complaint: make fields always private (or protected), especially when they are property backing fields. If you don't use any access modifier then internal will apply implicitly. Which is equivalent to public inside a shared assembly and fields should never be exposed.
Microsoft Docs recommends :
Generally, you should use fields only for variables that have private or protected accessibility. Data that your class exposes to client code should be provided through methods, properties and indexers. By using these constructs for indirect access to internal fields, you can guard against invalid input values. A private field that stores the data exposed by a public property is called a backing store or backing field.
App.xaml
<Application x:class="App">
<Application.Resources>
<ResourceDictionary>
<UserAccountViewModel x:Key="UserAccountViewModel" />
<LaptopManagementViewModel x:Key="LaptopManagementViewModel">
<LaptopManagementViewModel.UserAccountVM>
<StaticResource ResourceKey="UserAccountViewModel" />
</LaptopManagementViewModel.UserAccountVM>
</LaptopManagementViewModel>
</Application.Resources>
</Application>
LaptopWindow.xaml
<Window x:class="LaptopWindow">
<Window.DataContext>
<StaticResource ResourceKey="LaptopManagementViewModel" />
</Window.DataContext>
...
</Window>
FindEmployeeUC.xaml
<Window x:class="FindEmployeeUC">
<Window.DataContext>
<StaticResource ResourceKey="UserAccountViewModel" />
</Window.DataContext>
<DataGrid
...
</DataGrid>
</Window>
LaptopManagementViewModel.cs
public class LaptopManagementViewModel
{
private UserAccountViewModel userAccountVM;
public UserAccountViewModel UserAccountVM
{
get => userAccountVM;
set
{
userAccountVM = value;
OnPropertyChanged(nameof(UserAccountVM));
if (userAccountVM != null)
{
// Always clean up event handlers to avoid memory leaks
userAccountVM.PropertyChanged -= UpdateReceiverIdOnPropertyChanged;
}
userAccountVM.PropertyChanged += UpdateReceiverIdOnPropertyChanged;
}
}
// The constructor is now parameterless for the use in XAML
public LaptopManagementViewModel()
{
LstDVUS = LaptopManagementBLL.Instance.GetDVUsageStatuses();
LstLaptop = LaptopManagementBLL.Instance.GetLaptopsInfo();
}
// UserAccountVM.PropertyChanged event handler
private void UpdateReceiverIdOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals(nameof(UserAccountViewModel.SelectedUA), StringComparison.OrdinalIgnoreCase))
{
ReceiverID = UserAccountVM.SelectedUA.EmpID;
}
}
private string receiverID;
public string ReceiverID
{
get { return receiverID; }
set
{
receiverID = value;
OnPropertyChanged(nameof(ReceiverID));
}
}
}
UserAccountViewModel.cs
public class UserAccountViewModel
{
private UserAccountModel selectedUA;
public UserAccountModel SelectedUA
{
get => selectedUA;
set
{
if(selectedUA!=value)
{
// Removed wrong creation of LaptopManagementViewModel instances
selectedUA = value;
OnPropertyChanged(nameof(SelectedUA));
}
}
}
}

How to bind an ItemsControl to a dependency property of a usercontrol?

Trying to understand this better.
I have an ItemsControl defined in my mainview something like this
<ItemsControl Grid.Column="0" Grid.Row="2"
ItemsSource="{Binding Notes}"
ItemTemplate="{Binding Source={StaticResource MyParagraph}}"
>
</ItemsControl>
in which I would like to use a DataTemplate:
<UserControl.Resources>
<DataTemplate x:Key="MyParagraph">
<v:InkRichTextView
RichText="{Binding ?????? "
</DataTemplate>
</UserControl.Resources>
The InkRichTextView is a view with a dependency property, RichText, being used to pass a paragraph from the ObservableCollection(InkRichViewModel) Notes in the mainview to the user control. That is, this works correctly for one paragragh:
<v:InkRichTextView RichText ="{Binding Path=Note}" Grid.Column="0" Grid.Row="0" />
where Note is defined as a paragraph in the MainView.
The problem is, how do I write the DataTemplate and the ItemsControl such that the ItemsControl can pass each paragraph from the observablecollection to the dependency property RichText in the InkRichTextView?
Thanks for any guidance.
(I hope this is understandable!)
Items control:
<ItemsControl x:Name="NotesItemsControl" Grid.Column="2" HorizontalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<local:InkRichTextView RichText="{Binding Note}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Code behind:
class InkRichViewModel : System.ComponentModel.INotifyPropertyChanged
{
#region Note (INotifyPropertyChanged Property)
private string _note;
public string Note
{
get { return _note; }
set
{
if (_note != value)
{
_note = value;
RaisePropertyChanged("Note");
}
}
}
#endregion
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string p)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(p));
}
}
}
public MainWindow()
{
InitializeComponent();
var item01 = new InkRichViewModel() { Note = "Note 01", };
var item02 = new InkRichViewModel() { Note = "Note 02", };
var item03 = new InkRichViewModel() { Note = "Note 03", };
var item04 = new InkRichViewModel() { Note = "Note 04", };
var item05 = new InkRichViewModel() { Note = "Note 05", };
var itemList = new List<InkRichViewModel>()
{
item01, item02, item03, item04, item05,
};
NotesItemsControl.ItemsSource = itemList;
}
How it looks at runtime:
Is that what you're looking for?
Based on what you describe, it seems that each item in your ItemsControl is a paragraph, the very object you want to assign to the InkRichTextView.RichText property. Is that correct?
If so, keep in mind that within the item template, the data context is the collection item itself - thus, the path you are looking for does not refer to a property of the data context, but to the data context itself.
That is done with the dot (.) path:
<v:InkRichTextView RichText="{Binding .}"/>
I'm posting this as an answer, although the credit goes to O.R.Mapper and Murven for pointing me in the right direction. My post is to help anyone else just learning this.
In very simple terms, the ItemControl performs a looping action over the collection in its ItemsSource. In my case the ItemsSource is a collection of type InkRichViewModel. (Hence the question from Murven). In its looping action, the ItemsSource will create objects from the InkRichViewModel. (Thus, my usercontrol now has an individual datacontext!) Each of these objects will use the ItemTemplate for display. So to simplify things, I moved the DataTemplate from the UserControl Resources to within the ItemControl itself as:
<ItemsControl Grid.Column="0" Grid.Row="2"
ItemsSource="{Binding Notes}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<v:InkRichTextView RichText="{Binding Note}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now that each of my usercontrols has its own datacontext being assigned by the ItemsControl, the Output window (VS2010) now shows the binding errors. Fixing these errors leads to a working solution.
Hope this helps other newbies like myself. Thanks everyone.
(Ooops! Just saw the answer from Murven but I'll leave this if it helps somebody to understand.)

Binding to 2 Datasources

I want to bind my Datatemplate to 2 Datasources, one datasource that will actually define what is in the ListBox and other that will determine how many ListBoxes are there and what Items in the Listbox are selected\checked.
I have following XAML
<Window x:Class="WpfApplication1.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>
<DataTemplate x:Key="TokenListTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="chkToken" IsChecked="{Binding Path=IsSelected, Mode=TwoWay}">
<TextBlock Text="{Binding Path=Text}" />
</CheckBox>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ItemTemplate">
<Border BorderThickness="1">
<StackPanel Margin="3">
<TextBlock Text="{Binding Path=Header}"/>
<ListBox ItemTemplate="{StaticResource TokenListTemplate}"
ItemsSource="{Binding Path=Tokens}" >
</ListBox>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
And this is the codebehind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<DataEntity> _actualObjects;
List<Token> tokens1 = new List<Token>()
{
new Token("1"),
new Token("2"),
new Token("3"),
new Token("4")
};
List<Token> tokens2 = new List<Token>()
{
new Token("11"),
new Token("21"),
new Token("31")
};
_actualObjects = new ObservableCollection<DataEntity>()
{
new DataEntity(tokens1, "A", "1,2,3", 1),
new DataEntity(tokens1, "B", "2,3", 1),
new DataEntity(tokens2, "C", "21,31", 2)
};
DataContext = _actualObjects;
}
class DataEntity
{
public DataEntity(List<Token> tokens, string header, string tokenString, int entityTypeId)
{
Tokens = tokens;
Header = header;
TokenString = tokenString;
EntityTypeId = entityTypeId;
}
public List<Token> Tokens { get; set; }
public String Header { get; set; }
public String TokenString { get; set; }
public int EntityTypeId { get; set; }
}
public class Token
{
public bool IsSelected { get; set; }
public string Text { get; set; }
public Token(string text)
{
this.IsSelected = false;
this.Text = text;
}
}
}
It produces this
I don't want to inject token1 or token2 List into DataEntity object so in other words I want DataEntity constructor to be
public DataEntity(string header, string tokenString, int entityTypeId)
Listbox DataTemplate should select
tokens1 List as datasource for its LisBoxItems if
Dataentity.EntityTypeId = 1
tokens2 List as datasource for its LisBoxItemsif
DataEntity.EntityTypeId = 2
Also TokenString in DataEntity should be bound to items in the Listbox i.e. if Listbox shows 1 2 3 4
and DataEntity for this listbox has its TokenString value set to "1,2,3" then 1 2 3 should be checked in the listbox
I would recommend to create a ViewModel as a layer between your model and the view. In the ViewModel you can arrange the data to fit to the used controls without changing your model.
So the ViewModel could for example split the tokenString of the DataEntity into a list of tokens.
Just Google for MVVM (Model-View-ViewModel) for examples and furter explanations or look here on SO (like MVVM: Tutorial from start to finish?).
You're not thinking about this correctly. You need to create one class (some may call a view model) with the responsibility of providing all of the data that the view (or UI) will need. Therefore, you will need to have one property which holds a collection of type DataEntity (if I understand you correctly) to 'define what is in the outer ListBox' as you say.
Then you need a DataTemplate to describe what should be displayed for each item in the ListBox - your 'ItemTemplate' template. This DataTemplate should have another ListBox inside in which to display your Token objects. Your DataEntity should have something like this property in it:
public List<Token> Tokens
{
get
{
if (EntityTypeId == 1) return tokens1;
else if (EntityTypeId == 2) return tokens2;
}
}
You will then need another DataTemplate for your Token objects - your 'TokenListTemplate' template, but without the StackPanel... the inner ListBox replaces that, eg. if there are two Token objects in one DataEntity object, then that object would show two Checkboxes... you have correctly bound the IsChecked property to the Token.IsSelected property.
This may be complicated, but it is entirely possible. Just start with the first layer and get your DataEntity objects displayed in the outer ListBox using your 'ItemTemplate' template. Once that bit is ok, move on to the inner ListBox. Good luck.

Inverted TwoWay-MultiBinding

I'm trying to express an enumeration property in my view-model as a set of radio buttons in my view. So far, so good; I can express that with a two-way MultiBinding:
(rb1.IsChecked, rb2.IsChecked, rb3.IsChecked) <-> vm.Value
The multi-binding used here would feature a multi-converter that converts between (bool, bool, bool) <-> MyValue; obviously, one of the (three) allowable values of the MyValue type is chosen based on which bool is true, and vice-versa.
This is already a bit inconvenient, though: I cannot define that binding in my view's Xaml, as multi-bindings have to be defined from the side of the single value. Hence, I have to define the multi-binding in code-behind and use SetBinding set it on my view model's Value property.
Now, the issue that I'm stuck at is that I'm not just binding one set of radio buttons to that value, but two. Hence, my bindings would have to look like this:
(rbA1.IsChecked, rbA2.IsChecked, rbA3.IsChecked) <-> vm.Value <-> (rbB1.IsChecked, rbB2.IsChecked, rbB3.IsChecked)
The problem is that I cannot use SetBinding to connect several bindings to vm.Value at a time.
Solutions that I have tried so far are:
Use one big multi-binding, binding to all radio buttons at a time. This would mean a binding of the form (rbA1.IsChecked, rbA2.IsChecked, rbA3.IsChecked, rbB1.IsChecked, rbB2.IsChecked, rbB3.IsChecked) <-> vm.Value. The problem with this solution is that if one of the radio buttons (say, rbB2) is checked, I have no way of telling whether rbA2 (unchecked) or rbB2 (checked) has the "new, correct" value.
Abstract the radio button groups first by defining a radio group control that exposes only one SelectedIndex property. This property can then be conveniently bound to my vm.Value property from all instances of my radio group control. While feasible, it requires writing a new control class and I wonder whether this is the only way in WPF.
Bind one set of radio buttons to another one: By two-way-binding rbB1 to rbA1, rbB2 to rbA2, and so on, and using a multi-binding between my vm.Value and the first set of radio buttons only, I would achieve the desired effect, but I don't like the notion of having a "master radio group". It would be abusing GUI elements for data transfer.
Do everything in code-behind and manually update radio buttons and view-model value. Of course this is a viable fallback solution, but this feels like it should be feasible in Xaml/with bindings.
Bind VMEnum to each RadioButton seperately(use single two-way binding). Each binding should have CommandParameter it's enum. Like:
<RadioButton IsChecked="{Binding VMEnum, Converter={StaticResource EnumConverter}, ConverterParameter={Enums:VMEnums.FirstRadioButtonGroupA}}" />
In the converter,
Convert should return correct value(true/false) depending of the VMEnum and COmmandParameter. Essentially the logic is VMEnum == (YourEnum)CommandParameter.
ConvertBack should return correct Enum based on IsChecked. If IsChecked is true, return the correct enum. Otherwise return Binding.DoNothing which will abort the binding for that specific case.
Using complex multibinding with converters and codebehind will not only make your code harder to debug but even harder to test. In my opinion it's better to express each set of radio buttons (flags) as a view model. Evaluate your value when any of the radio buttons is checked/unchecked.
RadioButtonGroup
public class RadioButtonGroup : ViewModel {
public RadioButtonGroup(string groupName, int count, Action<bool[]> whenAnyChanaged = null) {
RadioButtons = Enumerable.Range(0, count).Select(_ => {
var button = new RadioButton { GroupName = groupName };
button.PropertyChanged += (s, e) => {
if (e.PropertyName == "IsChecked")
whenAnyChanaged(Flags);
};
return button;
}).ToList();
}
public List<RadioButton> RadioButtons { get; private set; }
public bool[] Flags { get { return RadioButtons.Select(rb => rb.IsChecked).ToArray(); } }
}
RadioButton
public class RadioButton : ViewModel {
private bool isChecked;
public bool IsChecked {
get { return isChecked; }
set { SetProperty(ref this.isChecked, value); }
}
public string GroupName { get; set; }
}
MainViewModel
public class MainViewModel : ViewModel {
public MainViewModel() {
GroupA = new RadioButtonGroup("A", 10, flags => GroupToggle(flags, GroupB.Flags));
GroupB = new RadioButtonGroup("B", 10, flags => GroupToggle(GroupA.Flags, flags));
}
public RadioButtonGroup GroupA { get; private set; }
public RadioButtonGroup GroupB { get; private set; }
void GroupToggle(bool[] groupA, bool[] groupB) {
MyValue = Evaluate(groupA, groupB);
}
}
View
<Window x:Class="WpfLab.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="RadioButton">
<RadioButton IsChecked="{Binding IsChecked, Mode=OneWayToSource}" GroupName="{Binding GroupName}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" ItemsSource="{Binding GroupA.RadioButtons}" ItemTemplate="{StaticResource ResourceKey=RadioButton}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
<ListBox Grid.Row="1" ItemsSource="{Binding GroupB.RadioButtons}" ItemTemplate="{StaticResource ResourceKey=RadioButton}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>

Table layout in WPF

I'm very new to WPF, so I've just started making a very simple Memory card game just to learn the syntax and such. The game is where all the cards are facing down, you flip two over and if they match you remove them, otherwise reflip them down and try to remove all the cards in the shortest number of flips. Like I said, very simple... :)
My question is, is there no table element like in HTML so I can easily put the cards in a uniform layout instead of having to mess with margins?
Here's an example with it using the UniformGrid as Matt Hamilton suggested.
First, lets create the classes and data that we will be using.
Each card will be represented by a Card object, and have a Face property:
public class Card
{
public string Face { get; set; }
public Card() { }
}
Next, we will need a class that has our collection of Cards, and also a property that lets us set the number of cards. For the CardCollection we can use an ObservableCollection since that will automaticaly notify the UI when a Card is added or removed. The NumberOfCards property will need it's own method to notify the UI, for this we can implement the INotifyPropertyChanged interface. We'll also want a property that represents the number of Rows/Columns to use, this will just be the square root of our NumberOfCards:
public class Cards : INotifyPropertyChanged
{
private int myNumberOfCards;
public int NumberOfCards
{
get { return this.myNumberOfCards; }
set
{
this.myNumberOfCards = value;
NotifyPropertyChanged("NumberOfCards");
// Logic is going in here since this is just an example,
// Though I would not recomend hevily modifying the setters in a finalized app.
while (this.myNumberOfCards > CardCollection.Count)
{
CardCollection.Add(new Card { Face = (CardCollection.Count + 1).ToString() });
}
while (this.myNumberOfCards < CardCollection.Count)
{
CardCollection.RemoveAt(CardCollection.Count - 1);
}
NotifyPropertyChanged("CardColumns");
}
}
public int CardColumns
{
get
{
return (int)Math.Ceiling((Math.Sqrt((double)CardCollection.Count)));
}
}
private ObservableCollection<Card> myCardCollection;
public ObservableCollection<Card> CardCollection
{
get
{
if (this.myCardCollection == null)
{ this.myCardCollection = new ObservableCollection<Card>(); }
return this.myCardCollection;
}
}
public Cards(int initalCards)
{
NumberOfCards = initalCards;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
Finally, we can set this as our DataContext in the Window, and bind to our Cards class in the XAML. For the XAML I used a simple ItemsControl, so that it isn't selectable, and I set the DataTemplate to be a button, so that each card can be clicked on, that's all that is needed!
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new Cards(25);
}
}
<Window x:Class="Sample_BoolAnimation.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Height="300"
Width="300">
<Grid>
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<TextBlock Text="Number of Cards:" />
<TextBox Text="{Binding NumberOfCards, UpdateSourceTrigger=PropertyChanged}" />
</DockPanel>
<ItemsControl ItemsSource="{Binding CardCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding CardColumns}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Face}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
</Grid>
</Window>
Another thing that I would recomend looking at is Josh Smith's ContentControl3D implementation. As that can give you the 'flipping' behavior that you are looking for implementing in the Card class quite nicely.
I'd recommend UniformGrid for your scenario. A quick search yielded this article which includes some code and screenshots that might help.
There is a Table in WPF, here's a good article on getting started with it. From experience the Table in WPF is not that easy to use and using a Grid is generally a better option.

Resources