|
What's the best way to make a Chess board using WPF and MVVM?
On startup I want to create an 8x8 grid of Squares.
Right now I added all 64 Sqaures to a <grid> in the XAML. But to make this work with MVVM I'll need some form of binding.
Not sure of how to architect this.
Anyone?
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Kevin Marois wrote: What's the best way to make a Chess board using WPF and MVVM? I asked myself the same question before working on StockChess[^] and I'm guessing you've figured out by now that the approach you've mentioned will not get you the result you want. If you want it to be MVVM friendly you will just have to create an items control that uses a Grid for its ItemsPanelTemplate . If you use an items control that enables you to select an item then even better, like a ListBox .
"As beings of finite lifespan, our contributions to the sum of human knowledge is one of the greatest endeavors we can undertake and one of the defining characteristics of humanity itself"
|
|
|
|
|
I have a MetroTabControl that I want to use to display several RDP connections using the TabItems, the problem that I`m having is, after I establish the first RDP connection, If I switch tabItem, the view goes out of scope and I loose the connection in that TabItem (so each time I switch tabitem the view goes out of scope). I wanted to maintain the connection alive in each TabItem and control the close of the view with the CloseTabCommand using the tabitem CloseButton.
In this sample the RemoteClients is an Observable collection of an objectClass that each TabItem will bind to
e.g. ObservableCollection<RdpClient>RemoteClients{get; set;}
The SelectedRemoteClient represents the current selected object (RdpClient).
RemoteDesktopBaseControlView is the view that each TabItem will have and has all the bindings for the RdpClient object
<Grid>
<controls:MetroTabControl VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding RemoteClients, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedRemoteClient, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
CloseTabCommand="{Binding CloseTabCommand}" >
<controls:MetroTabControl.Resources>
<DataTemplate x:Key="contentTemplate" x:Shared="False">
<local:RemoteDesktopBaseControlView />
</DataTemplate>
</controls:MetroTabControl.Resources>
<controls:MetroTabControl.ItemContainerStyle>
<Style TargetType="{x:Type controls:MetroTabItem}">
<Setter Property="Header" Value="{Binding ViewTitle, UpdateSourceTrigger=PropertyChanged}" />
<Setter Property="controls:MetroTabItem.ContentTemplate" Value="{StaticResource contentTemplate}"/>
<Setter Property="controls:MetroTabItem.CloseButtonEnabled" Value="True"/>
<Setter Property="controls:MetroTabItem.CloseTabCommandParameter" Value="{Binding}" />
<Setter Property="controls:ControlsHelper.HeaderFontSize" Value="16" />
<Setter Property="controls:ControlsHelper.HeaderMargin" Value="4" />
</Style>
</controls:MetroTabControl.ItemContainerStyle>
</controls:MetroTabControl>
</Grid>
Any Ideas how to fix this?
|
|
|
|
|
I have no idea what MetroTabControl is (other than a TabControl), but it sounds like it inherits the problem from the standard TabControl whereby switching tabs closes the view and creates a new one. If this is the case, you'll need to put in a version of the fix described here[^].
This space for rent
|
|
|
|
|
Hi, you`re right the controls:MetroTabControl is just a stylized version of the TabControl, removing the "controls:Metro" and leaving the <tabcontrol the="" behavior="" is="" same.="" i="" try="" extension="" but="" not="" working="" still="" same="" and="" it="" leaks="" memory.="" created="" tabcontrolex.cs="" add="" <style="" targettype="{x:Type local:TabControlEx}"> in the <usercontrol.resources> for the view but no change... do I need to do something else?
Thank you
|
|
|
|
|
|
I'm creating a chess game.
The board is made up of a grid of user controls called BoardSqaure:
<UserControl x:Class="Chess.UI.WPF.Controls.BoardSquare"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Chess.UI.WPF.Controls"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<ContentPresenter Content="{Binding Piece}"/>
</UserControl>
and
namespace Chess.UI.WPF.Controls
{
public partial class BoardSquare : UserControl
{
public BoardSquare()
{
InitializeComponent();
}
public static readonly DependencyProperty PieceProperty =
DependencyProperty.Register("Piece",
typeof(PieceBase),
typeof(BoardSquare),
new PropertyMetadata(null));
public PieceBase Piece
{
get { return (PieceBase)GetValue(PieceProperty); }
set { SetValue(PieceProperty, value); }
}
}
}
The DP Piece will hold an instance of PieceBase, which can be King, Queen, Rook.. etc.
To load the board in the code behind I'm trying to do
private void SetupBoard()
{
BoardSquare square = board.Children.Cast<BoardSquare>().First(e => Grid.GetRow(e) == 0 && Grid.GetColumn(e) == 0);
square.Piece = new RookBlack();
square = board.Children.Cast<BoardSquare>().First(e => Grid.GetRow(e) == 0 && Grid.GetColumn(e) == 1);
square.Piece = new BishopBlack();
.
.
.
}
However the pieces never shows up.
What am I doing wrong here?
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
modified 5-Dec-16 17:08pm.
|
|
|
|
|
Kevin Marois wrote: BoardSquare square = board.Children.Cast<BoardSquare>().First(e => Grid.GetRow(e) == 0 && Grid.GetColumn(e) == 0);
square.Piece = new RookBlack();
square = board.Children.Cast<BoardSquare>().First(e => Grid.GetRow(e) == 0 && Grid.GetColumn(e) == 1);
square.Piece = new BishopBlack();
Well first off the bishop doesn't go next to the rook In all seriousness though, I see nothing wrong with the code you've posted. Could you edit with all relevant Queen code? I'm assuming the other pieces are displaying properly?
|
|
|
|
|
No piece that I assign to the Piece property shows up
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
There's really no way to tell from the code posted what's going on. ContentPresenter is generally used in a ControlTemplate . Using it raw like this means there are SOO many variables that could affect what's going on. Is there a DataTemplate associated with the type? What type of element is PieceBase ? Does it have any templates associated with it?
ContentPresenter is a very dynamic class. You can see just how many different things it checks and may or may not apply in the remarks section of its MSDN page[^].
|
|
|
|
|
OK, so what would be the right architecture here for a chess board? Keep in mind that the user will drag/drop pieces around, and pieces will also need to be programmatically added & removed.
Thanks
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
You can programmatically remove anything. For drag and drop, the MSDN article Walkthrough: Enabling Drag and Drop on a User Control[^] has everything you need. As far as architecture, that's entirely a personal choice.
You can derive your UserControl from ContentControl and use a ContentPresenter . You could use Image controls[^] inside each Grid square. I mean WPF is a huge and flexible architecture.
EDIT: I forgot to mention, you can use the ContentPresenter like that - nothing inherently wrong with it. There's just not enough information for me to say why it might not be working without all the code that could affect it.
|
|
|
|
|
There's too many things that you may have accounted for that we can't see:
Width
Height
Visibility
Transparency
Z-Order
Background color
Foreground color
Top
Left
Rendering
...
|
|
|
|
|
In a chess app I wrote some time back: WPF: P2P Chess[^], I took an approach similar to what you're doing. I had a bunch of user controls to represent the pieces. Over time I grew uncomfortable with this and decided to take an MVVM friendly approach for an app I'm currently developing, where the user plays against Stockfish. The chess board in my current app is a ListBox whose ItemsPanel is a Grid with eight row and eight columns. The ItemsSource property of the ListBox is bound to a collection of objects that implement the same interface. The collection, in the View Model, is made up of BoardSquare s and ChessPiece s. The DataTemplate of the ListBox is an Image that uses MultiDataTrigger s to determine the appropriate image to display depending on the type of object. I'll be posting an article soon, maybe tommorrow, where you can dig deeper into the code.
"As beings of finite lifespan, our contributions to the sum of human knowledge is one of the greatest endeavors we can undertake and one of the defining characteristics of humanity itself"
|
|
|
|
|
I look forward to seeing it.
One question... When setting up the board, the board is layout grid with 8 rows & 8 columns, and the pieces are added to the grid during setup.
How do you reverse the board for the other player?
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Kevin Marois wrote: How do you reverse the board for the other player? By reverse I guess you mean flip. I rotate the ListBox and switch its DataTemplate . One DataTemplate has the Image control rotated at an angle of 0 while the other has the Image control rotated at an angle of 180 degrees.
"As beings of finite lifespan, our contributions to the sum of human knowledge is one of the greatest endeavors we can undertake and one of the defining characteristics of humanity itself"
|
|
|
|
|
Check the Visual Studio output window, and you'll probably find a load of binding errors telling you that the property Piece doesn't exist in the data context of the parent window.
Update the user control so that it points to itself for the DataContext :
public BoardSquare()
{
InitializeComponent();
DataContext = this;
}
Or:
<UserControl x:Class="Chess.UI.WPF.Controls.BoardSquare"
...
DataContext="{Binding RelativeSource={RelativeSource Self}}"
>
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
You were right. Thanks!
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Hello!
I am trying to make a Contact Book, where i can have my contacts and conversations(+other activities related to them) in C# WPF.I am also trying to learn MVVM with this.
I have 2 listboxes each bound to an observable collection populated from a database using LinqToSQL.
Listbox 1(ContactsLstBx) is displaying a list of contact names, and in Listbox 2(ConversationLstBx), i want to display the conversations i had with the selected contact in Listbox 1.
The problem i have is that(besides being a "noob") the SelectedContact property, does not update after i set it in MainViewModel constructor. If i set it manually it displays the conversation list correctly based on the ContactId.
What i am trying to achieve is: when the program starts, the only listbox loaded is the ContactsLstBxd after i select a contact, to get the SelectedContact, and load the other listbox.
Should i use a SelectionChanged event? Can i load the conversation listbox if i add a call to PopulateConversationList() in my SelectedContact setter?
Any help/advice is greatly apreciated.
Thanks!
C#:
public class MainViewModel : ViewModelBase
{
private Contact selectedContact;
public ObservableCollection<Contact> ContactList;
public ObservableCollection<Conversation> ConversationList;
public MainViewModel()
{
ContactList = new ObservableCollection<Contact>(cbdc.Contacts);
SelectedContact = ContactList.FirstOrDefault();
PopulateConversationList();
}
public Contact SelectedContact
{
get { return selectedContact; }
set
{
if (selectedContact == value)
return;
selectedContact = value;
RaisePropertyChanged("SelectedContact");
}
}
public void PopulateConversationList()
{
if (selectedContact != null)
{
var conversations = from c in cbdc.Conversations
where c.ContactID == SelectedContact.Id
select c;
ConversationList = new ObservableCollection<Conversation>(conversations);
}
}
public class ViewModelBase: INotifyPropertyChanged
{
public ContactLinqToSQLClassesDataContext cbdc = new ContactLinqToSQLClassesDataContext();
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
internal void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
XAML:
<ListBox x:Name="ContactsLstBx" HorizontalAlignment="Left" Height="289" Margin="10,50,0,0" VerticalAlignment="Top" Width="115" ItemsSource="{Binding ContactList}" SelectedItem="{Binding SelectedContact, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox x:Name="ConversationLstBx" HorizontalAlignment="Left" Height="134" Margin="333,50,0,0" VerticalAlignment="Top" Width="115" ItemsSource="{Binding ConversationList}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Title}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
|
|
|
|
|
You're replacing the entire conversation list, so you need to make it a property, and call RaisePropertyChanged when you replace it.
You'll also need to call PopulateConversationList when the selected contact changes.
You can simplify the calls to RaisePropertyChanged by using the CallerMemberName attribute[^] on the parameter. You should also store the event handler delegate in a local variable before testing and calling it, since other threads might modify it between the null test and the invocation.
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public class MainViewModel : ViewModelBase
{
private Contact selectedContact;
private ObservableCollection<Contact> contactList;
private ObservableCollection<Conversation> conversationList;
public MainViewModel()
{
contactList = new ObservableCollection<Contact>(cbdc.Contacts);
SelectedContact = contactList.FirstOrDefault();
}
public ObservableCollection<Contact> ContactList
{
get { return contactList; }
}
public Contact SelectedContact
{
get
{
return selectedContact;
}
set
{
if (selectedContact != value)
{
selectedContact = value;
RaisePropertyChanged();
PopulateConversationList();
}
}
}
public ObservableCollection<Conversation> ConversationList
{
get
{
return conversationList;
}
private set
{
if (conversationList != value)
{
conversationList = value;
RaisePropertyChanged();
}
}
}
private void PopulateConversationList()
{
if (selectedContact == null)
{
ConversationList = null;
}
else
{
var conversations = from c in cbdc.Conversations
where c.ContactID == SelectedContact.Id
select c;
ConversationList = new ObservableCollection<Conversation>(conversations);
}
}
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Thank You!
I have modified my code, but my SelectedContact still doesnt update.
The XAML code for the listbox bindings remains the same as in my first post.
What should i do next?
(sorry if its something obvious that i havent noticed)
public class MainViewModel : ViewModelBase
{
private Contact selectedContact;
private ObservableCollection<Contact> contactList;
private ObservableCollection<Conversation> conversationList;
public MainViewModel()
{
ContactList = new ObservableCollection<Contact>(cbdc.Contacts);
SelectedContact = ContactList.FirstOrDefault();
}
public ObservableCollection<Contact> ContactList
{
get
{
return contactList;
}
private set
{
if (contactList != value)
{
contactList = value;
RaisePropertyChanged();
}
}
}
public ObservableCollection<Conversation> ConversationList
{
get
{
return conversationList;
}
private set
{
if (conversationList != value)
{
conversationList = value;
RaisePropertyChanged();
}
}
}
public Contact SelectedContact
{
get
{
return selectedContact;
}
set
{
if (selectedContact != value)
{
selectedContact = value;
RaisePropertyChanged();
PopulateConversationList();
}
}
}
public void PopulateConversationList()
{
if (selectedContact == null)
{
ConversationList = null;
}
else
{
var conversations = from c in cbdc.Conversations
where c.ContactID == SelectedContact.Id
select c;
ConversationList = new ObservableCollection<Conversation>(conversations);
}
}
}
public class ViewModelBase: INotifyPropertyChanged
{
public ContactLinqToSQLClassesDataContext cbdc = new ContactLinqToSQLClassesDataContext();
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
internal void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
public class ViewModelBase: INotifyPropertyChanged
{
public ContactLinqToSQLClassesDataContext cbdc = new ContactLinqToSQLClassesDataContext();
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
internal void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainViewModel cvm = new MainViewModel();
ContactsLstBx.ItemsSource = cvm.ContactList;
ConversationLstBx.ItemsSource = cvm.ConversationList;
}
}
|
|
|
|
|
ItemsSource binds to a collection. There is a default CollectionView generated that is what you are actually seeing. The CollectionView listens for a CollectionChanged event not a PropertyChanged event. Now, you may assume that ObservableCollection handles all this for you, and generally it does. But you are not performing any actions that are monitorable by this class (Add, Move, Remove, Replace, Reset[^]) and instead are completely replacing the instance itself. Try this:
public void PopulateConversationList()
{
if (selectedContact == null)
{
ConversationList = null;
}
else
{
var conversations = from c in cbdc.Conversations
where c.ContactID == SelectedContact.Id
select c;
ConversationList.Clear();
foreach (Conversation c in conversations)
{
ConversationList.Add(c);
}
}
}
I highly recommend anyone learning WPF to read the MSDN Data Binding Overview[^] article. If you skip down to "Binding to Collections" you'll see relevant information for both your question and my answer
Haven't had a chance to actually run the code but this definitely seems like the issue since I've run into the same problem before.
|
|
|
|
|
I have modified the PopulateConversationList() method, and before
ConversationList.Clear();
i have added
if (ConversationList == null)
{
ConversationList = new ObservableCollection<Conversation>();
}
because i was getting a NullRefferenceException.
The code works as before(SelectedContact doesnt updates).
What else should i check?
public void PopulateConversationList()
{
if (selectedContact == null)
{
ConversationList = null;
}
else
{
var conversations = from c in cbdc.Conversations
where c.ContactID == SelectedContact.Id
select c;
if (ConversationList == null)
{
ConversationList = new ObservableCollection<Conversation>();
}
ConversationList.Clear();
foreach (Conversation c in conversations)
{
ConversationList.Add(c);
}
}
|
|
|
|
|
The whole point of my post was to remove the re-assignment. Unless you want to implement your own CollectionChanged event to cover the re-assignment, the default CollectionView won't know the collection was modified. I forgot to change the null assignment in your if-statement. Also, you should instantiate in your constructor.
public MainViewModel()
{
ContactList = new ObservableCollection<Contact>(cbdc.Contacts);
ConversationList = new ObservableCollection<Conversation>();
SelectedContact = ContactList.FirstOrDefault();
PopulateConversationList();
}
public void PopulateConversationList()
{
if (selectedContact == null)
{
ConversationList.Clear();
}
else
{
var conversations = from c in cbdc.Conversations
where c.ContactID == SelectedContact.Id
select c;
ConversationList.Clear();
foreach (Conversation c in conversations)
{
ConversationList.Add(c);
}
}
If you really want to remake the collection each time you can but you will have to implement INotifyCollectionChanged to cover your re-assignment which is not as friendly as INotifyPropertyChanged . That's a lot of extra mess when you can simply re-use the collection like this.
|
|
|
|
|
I have modified the code and i get the same results as before.
Sorry, i dont want to remake the collection, but i am a little(maybe a little more) confused.
Should i check something else?
public class MainViewModel : ViewModelBase
{
private Contact selectedContact;
private ObservableCollection<Contact> contactList;
private ObservableCollection<Conversation> conversationList;
public MainViewModel()
{
ContactList = new ObservableCollection<Contact>(cbdc.Contacts);
ConversationList = new ObservableCollection<Conversation>();
SelectedContact = ContactList.FirstOrDefault();
PopulateConversationList();
}
public ObservableCollection<Contact> ContactList
{
get
{
return contactList;
}
private set
{
if (contactList != value)
{
contactList = value;
RaisePropertyChanged();
}
}
}
public Contact SelectedContact
{
get
{
return selectedContact;
}
set
{
if (selectedContact != value)
{
selectedContact = value;
RaisePropertyChanged();
PopulateConversationList();
}
}
}
public ObservableCollection<Conversation> ConversationList
{
get
{
return conversationList;
}
private set
{
if (conversationList != value)
{
conversationList = value;
RaisePropertyChanged();
}
}
}
public void PopulateConversationList()
{
if (selectedContact == null)
{
ConversationList.Clear();
}
else
{
var conversations = from c in cbdc.Conversations
where c.ContactID == SelectedContact.Id
select c;
ConversationList.Clear();
foreach (Conversation c in conversations)
{
ConversationList.Add(c);
}
}
}
}
|
|
|
|