I am using Caliburn Micro in my Project and i have many UserControls and thier viewmodel inherited from PropertyChangedBase, i want this UserControl to be added to a Canvas in my ShellView. I dont want to use IWindowManager from showing Windows instead i want them to get added in a Canvas.
Please help. How can i do that.
If you use ContentControl within your ShellView you can hook into the View-ViewModel binding process of Caliburn.Micro.
I assume that in your ShellViewModel you have a bunch of properties exposed that are types of ViewModel. If you place a ContentControl in your ShellView (this could be on/as a child of Canvas if that is the container you wish to use to layout your Shell), and then name that control with the name of the property in your ShellViewModel you wish it to be bound to, then Caliburn's ViewModelBinder will do the rest for you.
As an example say you have a VM called FizzViewModel and a matching View called FizzView (which is just a UserControl) and you want FizzView to appear on your ShellView you could do something like the following...
A stripped back ShellViewModel
public class ShellViewModel : Screen, IShell
{
public ShellViewModel(FizzViewModel theFizz)
{
TheFizz = theFizz;
}
public FizzViewModel TheFizz { get; set; }
}
And its matching ShellView
<UserControl x:Class="ANamespace.ShellView">
<Canvas>
<ContentControl x:Name="TheFizz"></ContentControl>
</Canvas>
</UserControl>
Here because the ContentControl is named TheFizz, it will be bound by Caliburn to the property with that name on your VM (the one of type FizzViewModel)
Doing this means you don't have to laydown your UserControl's using their true types on your ShellView, you let Caliburn do the work for you via conventions (which all so means its easy to swap out the type TheFizz if you just add a little more interface indirection).
UPDATE
From the extra information you have provided in the comments, I can now see you are actually looking at a problem that requires an ItemsControl.
The default DataTemplate Caliburn uses looks like the following
<DataTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro">
<ContentControl cal:View.Model="{Binding}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch" />
</DataTemplate>
You will notice that it uses a ContentControl, which has some advantages as I have discussed above. Basically what this will do is allow Caliburn to provide DataTemplateSelector like behaviour to the items in your ItemsControl. So you can add VMs of different types to the collection your ItemsControl is bound to and this default DataTemplate will resolve the type of View to use to display it. The following demos a very simple example of how you can achieve what you want.
First the ShellViewModel, take note of the BindableCollection named Items
[Export(typeof(IShell))]
public class ShellViewModel : IShell
{
public ShellViewModel()
{
Items = new BindableCollection<Screen>();
_rand = new Random();
}
public BindableCollection<Screen> Items { get; set; }
private Random _rand;
public void AddItem()
{
var next = _rand.Next(3);
var mPosition = System.Windows.Input.Mouse.GetPosition(App.Current.MainWindow);
switch (next)
{
case 0:
{
Items.Add(new BlueViewModel
{
X = mPosition.X,
Y = mPosition.Y,
});
break;
}
case 1:
{
Items.Add(new RedViewModel
{
X = mPosition.X,
Y = mPosition.Y,
});
break;
}
case 2:
{
Items.Add(new GreenViewModel
{
X = mPosition.X,
Y = mPosition.Y,
});
break;
}
default:
break;
}
}
}
And then a few dummy VM types that you want to display in your Shell. These could be/do anything you like:
public abstract class SquareViewModel : Screen
{
public double X { get; set; }
public double Y { get; set; }
}
public class BlueViewModel : SquareViewModel
{
}
public class RedViewModel : SquareViewModel
{
}
public class GreenViewModel : SquareViewModel
{
}
Now a ShellView, note the ItemsControl which binds to the Items property on your ShellViewModel
<Window x:Class="WpfApplication2.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro">
<Grid >
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ItemsControl x:Name="Items"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas cal:Message.Attach="[Event MouseLeftButtonUp] = [Action AddItem()]"
Background="Transparent"></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</Window>
And an example of a UserControl that will be used to display the GreenViewModel, create 2 more of these, changing the names to RedView and BlueView and set the backgrounds appropriately to get the demo to work.
<UserControl x:Class="WpfApplication2.GreenView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="30"
Height="30">
<Grid Background="Green"></Grid>
</UserControl>
What this example does when put together is creates colored squares on the Canvas of your shell based on the location of the mouse click. I think you should be able to take this and extend it to your needs.
Related
I've found a really strange quirk in WPF. If I specify a DataTemplate for an interface, it will work if defined inside an ItemsControl.ItemTemplate, but will not work if defined inside ItemsControl.Resrouces.
Concrete example:
I have a tree structure I want to represent. All items in the tree implement IHardware, but they do not necessarily have a common base type. If I define a HierarchicalDataTemplate for IHardware inside TreeView.ItemTemplate, everything works swimmingly. If I define the template inside TreeView.Resources, it never gets used/applied. The following shows the same data in 2 columns, the first column works as expected, the second column does not.
<Window x:Class="WPFInterfaceBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self ="clr-namespace:WPFInterfaceBinding"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- Works -->
<Border
Grid.Column="0"
Background="Gray">
<TreeView
ItemsSource="{Binding Hardware}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
DataType="{x:Type self:IHardware}"
ItemsSource="{Binding SubHardware}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Border>
<!-- Doesn't work -->
<Border
Grid.Column="1"
Background="Gray">
<TreeView
ItemsSource="{Binding Hardware}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type self:IHardware}"
ItemsSource="{Binding SubHardware}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Border>
</Grid>
</Window>
Note that in the second column, nothing has changed except TreeView.ItemTemplate -> TreeView.Resources
Why is this the case? How can I get the template to work when inside Resources? I imagine I can work around this using a DataTemplateSelector, but first I'm curious if there's a way to actually get it working as expected.
Code behind, for completeness
using System.Windows;
namespace WPFInterfaceBinding
{
public partial class MainWindow : Window
{
public IHardware[] Hardware { get; private set; }
public MainWindow ()
{
Hardware = InitializeHardware();
InitializeComponent();
}
private IHardware[] InitializeHardware ()
{
return new Hardware[] {
new Hardware("Component 1", new Hardware[] {
new Hardware("Sub Component 1"),
new Hardware("Sub Component 2")
}),
new Hardware("Component 2", new Hardware[] {
new Hardware("Sub Component 3"),
new Hardware("Sub Component 4")
})
};
}
}
public class Hardware : IHardware
{
public string Name { get; set; }
public IHardware[] SubHardware { get; set; }
public Hardware ( string name, Hardware[] subHardware = null )
{
Name = name;
SubHardware = subHardware ?? new Hardware[0];
}
}
public interface IHardware
{
string Name { get; set; }
IHardware[] SubHardware { get; set; }
}
}
Additional information:
I can't simply use ItemTemplate because in my actual usage scenario there will be non-IHardware items mixed in using a CompositeCollection so I need multiple templates.
I can't change the types of the collections from IHardware to something concrete because I'm displaying data from code I don't control.
This is just example code, not representative of any design patterns actually in use.
Defining the template inside TreeView.Resources works just fine if the type is changed from IHardware to Hardware.
Turns out, WPF just doesn't like binding to interfaces. The only work around I could figure out was to use a DataTemplateSelector.
public class OHMTreeTemplateSelector : DataTemplateSelector
{
public HierarchicalDataTemplate HardwareTemplate { get; set; }
public DataTemplate SensorTemplate { get; set; }
public override DataTemplate SelectTemplate ( object item, DependencyObject container )
{
if ( item is IHardware ) return HardwareTemplate;
else if ( item is ISensor ) return SensorTemplate;
return base.SelectTemplate(item, container);
}
}
Though, for other reasons I ended up creating a separate ViewModel for the data that exposes it through concrete types, circumventing this issue.
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.
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>
I wonder how I can show design time data in Expression Blend that is located inside a SampleData.xaml using a CollectionViewSource? Before changing my code to use the CVS, I used an ObservableCollection. I was in the need to filter and sort the items inside there, thus I changed the code to use the CVS. Now my designer complains about not being able to fill the SampleData's NextItems with a proper structure to show up in Expression Blend. Here is some code I use inside the app:
MainViewModel.cs
class MainViewModel
{
public MainViewModel()
{
AllItems = new ObservableCollection<ItemViewModel>();
NextItems = new CollectionViewSource();
NextItems.Source = AllItems;
}
public CollectionViewSource NextItems
{
get;
private set;
}
public ObservableCollection<ItemViewModel> AllItems
{
get;
private set;
}
some functions to fill, filter, sort etc...
}
MainView.xaml:
<phone:PhoneApplicationPage
... some other stuff ...
d:DesignWidth="480"
d:DesignHeight="728"
d:DataContext="{d:DesignData SampleData/SampleData.xaml}">
<Grid
x:Name="LayoutRoot"
Background="Transparent">
<controls:Panorama>
<controls:PanoramaItem>
<ListBox ItemsSource="{Binding NextItems.View}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Image}" />
<StackPanel>
<TextBlock Text="{Binding FullName}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>
</controls:Panorama>
</Grid>
</phone:PhoneApplicationPage>
SampleData.xaml
<local:MainViewModel
xmlns:local="clr-namespace:MyAppNamespace"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:swd="clr-namespace:System.Windows.Data;assembly=System.Windows" >
<local:MainViewModel.AllItems>
<local:ItemModel
FullName="Dummy"
Image="/Images/dummy.png" />
</local:MainViewModel.AllItems>
<local:MainViewModel.NextItems>
How to fill the CollectionViewSource's Source?
</local:MainViewModel.NextItems>
</local:MainViewModel>
So the question I can't find an answer to is how to fill the Source for NextItems in SampleDate.xaml? Any help would be much appreciated.
if you want to show sample data in the designer I would recommend you to do it from code. There are two ways of generating sample data for the Blend Designer or the VStudio designer:
From an XML file as you do.
From a c# class -> Best option
best option.
In WPF, in windows 8 and in WP7.5 and highger, you can access a propertie called:Windows.ApplicationModel.DesignMode.DesignModeEnabled making use of it you can seed your ObservableCollection from your view model:
public class MainViewModel
{
public MainViewModel()
{
AllItems = new ObservableCollection<ItemViewModel>();
if (DesignMode.DesignModeEnabled)
{
AllItems = FakeDataProvider.FakeDataItems;
}
NextItems.Source = AllItems;
}
public CollectionViewSource NextItems
{
get;
private set;
}
public ObservableCollection<ItemViewModel> AllItems
{
get;
private set;
}
}
In this way, if you change the model, you dont' have to regenerate an XML file, it's a little bit cleaner from a C# file. The FakeDataProvider is an static class where all design-time fake data are stored. So in you XAML the only thing you have to do is to bind your Listbox to the collection of your ViewModel.
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.