Chris,
I don't know if you're still looking for a way to do this. I know that there are a lot of guides to similar problems, but not exactly what you're looking for. From my understanding of what you want, you want a control which has two faces (really x faces) where a user can push a button and cause the panel to "flip" and show different data. However, you want this data that is shown to be generic enough so that this flip panel can be used in other locations with just a different implementation rather than totally different code. Do I understand your needs correctly? If not, please clarify where I've gone astray and I can maybe get a better answer for you. With that being said, here is what I've done (Google code demo project at bottom):
- I created a control library that houses my FlipPanel (because that is how I do things; so that I may use the controls in other projects down the road.)
- I styled the control in the control library to contain the above described properties that you need in your scenario.
- I created a Silverlight 2.0 application to create an instance of the control.
- I created a basic object for binding that has a few properties so that I can demonstrate the control's potential.
Here is a possible definition to use in a Silverlight 2.0 page:
<Grid x:Name="LayoutRoot" Background="White">
<controls:FlipPanel x:Name="TestingFlipPanel" Side="Front" >
<controls:FlipPanel.BackDataTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Back: "/>
<TextBlock Text="{Binding BackText}" />
</StackPanel>
</DataTemplate>
</controls:FlipPanel.BackDataTemplate>
<controls:FlipPanel.FrontDataTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Front: "/>
<TextBlock Text="{Binding FrontText}" />
</StackPanel>
</DataTemplate>
</controls:FlipPanel.FrontDataTemplate>
</controls:FlipPanel>
</Grid>
The alternative to this is to define the data templates in the user control (page level, or even the app level) like this:
So that you have an idea of what my binding object looks like here is that definition (yes, it's VB... Sorry!):
Public Class BindingObject
Private _FrontText As String
Private _BackText As String
Public Sub New(ByVal frontText As String, ByVal backText As String)
MyBase.New()
_FrontText = frontText
_BackText = backText
End Sub
Public Property FrontText() As String
Get
Return _FrontText
End Get
Set(ByVal value As String)
_FrontText = value
End Set
End Property
Public Property BackText() As String
Get
Return _BackText
End Get
Set(ByVal value As String)
_BackText = value
End Set
End Property
End Class
In my code behind, here is the definition of my page and setting of the data context for the flip panel:
Partial Public Class Page
Inherits UserControl
Dim _BindingObject As New BindingObject("This is the front", "This is the back")
Public Sub New()
InitializeComponent()
End Sub
Private Sub Page_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
TestingFlipPanel.DataContext = _BindingObject
End Sub
End Class
So with this all laid out in front of you, we would expect that the control would display a button (in the control style) and a text block (actually two) that says "Front: This is the front" and when the button is pressed it is "flipped" to display "Back: This is the back".
With all that being said, here is the style that I used for the control:
<Style TargetType="controls:FlipPanel">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:FlipPanel">
<Grid x:Name="LayoutRoot">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button x:Name="FlipButton" Content="Flip" Grid.Row="0" />
<ContentPresenter Grid.Row="1" x:Name="FrontContentPresenter" Content="{TemplateBinding DataContext}" ContentTemplate="{TemplateBinding FrontDataTemplate}" />
<ContentPresenter Grid.Row="1" x:Name="BackContentPresenter" Content="{TemplateBinding DataContext}" ContentTemplate="{TemplateBinding BackDataTemplate}" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And finally, the control's definition (Caution - It's long):
<TemplatePart(Name:=FlipPanel.LayoutRoot_ElementName, Type:=GetType(FrameworkElement))> _
<TemplatePart(Name:=FlipPanel.FrontContentPresenter_ElementName, Type:=GetType(FrameworkElement))> _
<TemplatePart(Name:=FlipPanel.BackContentPresenter_ElementName, Type:=GetType(FrameworkElement))> _
<TemplatePart(Name:=FlipPanel.FlipButton_ElementName, Type:=GetType(FrameworkElement))> _
Public Class FlipPanel
Inherits Control
Public Const LayoutRoot_ElementName As String = "LayoutRoot"
Public Const FlipButton_ElementName As String = "FlipButton"
Public Const FrontContentPresenter_ElementName As String = "FrontContentPresenter"
Public Const BackContentPresenter_ElementName As String = "BackContentPresenter"
Public Enum Sides
Front
Back
End Enum
Private _LayoutRoot As FrameworkElement = Nothing
Private _FlipButton As FrameworkElement = Nothing
Private _FrontContentPresenter As FrameworkElement = Nothing
Private _BackContentPresenter As FrameworkElement = Nothing
Private _ControlUpdating As Boolean = False
Public Sub New()
MyBase.New()
MyBase.DefaultStyleKey = GetType(FlipPanel)
End Sub
Public Overrides Sub OnApplyTemplate()
MyBase.OnApplyTemplate()
UpdateControl()
End Sub
Private Sub UpdateControl()
If _ControlUpdating Then Exit Sub
_ControlUpdating = True
If _LayoutRoot Is Nothing Then _LayoutRoot = TryCast(GetTemplateChild(LayoutRoot_ElementName), FrameworkElement)
If _LayoutRoot IsNot Nothing Then
Dim element As Grid = TryCast(_LayoutRoot, Grid)
If element IsNot Nothing Then
' Update LayoutGrid here.
End If
End If
If _FlipButton Is Nothing Then _FlipButton = TryCast(GetTemplateChild(FlipButton_ElementName), FrameworkElement)
If _FlipButton IsNot Nothing Then
Dim element As Button = TryCast(_FlipButton, Button)
If element IsNot Nothing Then
' Update Button
RemoveHandler element.Click, AddressOf _FlipButton_Click
AddHandler element.Click, AddressOf _FlipButton_Click
End If
End If
If _FrontContentPresenter Is Nothing Then _FrontContentPresenter = TryCast(GetTemplateChild(FrontContentPresenter_ElementName), FrameworkElement)
If _FrontContentPresenter IsNot Nothing Then
Dim element As ContentPresenter = TryCast(_FrontContentPresenter, ContentPresenter)
If element IsNot Nothing Then
' Update FrontContentPresenter here.
If Side = Sides.Front Then
element.Visibility = Windows.Visibility.Visible
Else
element.Visibility = Windows.Visibility.Collapsed
End If
End If
End If
If _BackContentPresenter Is Nothing Then _BackContentPresenter = TryCast(GetTemplateChild(BackContentPresenter_ElementName), FrameworkElement)
If _BackContentPresenter IsNot Nothing Then
Dim element As ContentPresenter = TryCast(_BackContentPresenter, ContentPresenter)
If element IsNot Nothing Then
' Update BackContentPresenter here.
If Side = Sides.Front Then
element.Visibility = Windows.Visibility.Collapsed
Else
element.Visibility = Windows.Visibility.Visible
End If
End If
End If
_ControlUpdating = False
End Sub
Private Sub _FlipButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
Select Case Side
Case Sides.Front
Side = Sides.Back
Case Sides.Back
Side = Sides.Front
Case Else
Throw New ArgumentOutOfRangeException("Side")
End Select
UpdateControl()
End Sub
Private Sub FlipPanel_LayoutUpdated(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.LayoutUpdated
UpdateControl()
End Sub
#Region " FrontDataTemplateProperty Dependency Property "
#Region " FrontDataTemplate Property "
Public Property FrontDataTemplate() As DataTemplate
Get
Return DirectCast(GetValue(FrontDataTemplateProperty), DataTemplate)
End Get
Set(ByVal value As DataTemplate)
SetValue(FrontDataTemplateProperty, value)
End Set
End Property
#End Region
#Region " FrontDataTemplate Dependency Property "
Public Shared ReadOnly FrontDataTemplateProperty As DependencyProperty = DependencyProperty.Register("FrontDataTemplate", GetType(DataTemplate), GetType(FlipPanel), New PropertyMetadata(Nothing, AddressOf OnFrontDataTemplatePropertyChanged))
#End Region
#Region " FrontDataTemplate Property Changed CallBack "
Private Shared Sub OnFrontDataTemplatePropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If e.OldValue Is Nothing AndAlso e.NewValue Is Nothing Then Exit Sub
If e.OldValue IsNot Nothing AndAlso e.OldValue.Equals(e.NewValue) Then Exit Sub
Dim source As FlipPanel = TryCast(d, FlipPanel)
If source Is Nothing Then Throw New ArgumentException("source is not an instance of FlipPanel!")
' Provide any other validation here.
' Apply any other changes here.
End Sub
#End Region
#End Region
#Region " BackDataTemplateProperty Dependency Property "
#Region " BackDataTemplate Property "
Public Property BackDataTemplate() As DataTemplate
Get
Return DirectCast(GetValue(BackDataTemplateProperty), DataTemplate)
End Get
Set(ByVal value As DataTemplate)
SetValue(BackDataTemplateProperty, value)
End Set
End Property
#End Region
#Region " BackDataTemplate Dependency Property "
Public Shared ReadOnly BackDataTemplateProperty As DependencyProperty = DependencyProperty.Register("BackDataTemplate", GetType(DataTemplate), GetType(FlipPanel), New PropertyMetadata(Nothing, AddressOf OnBackDataTemplatePropertyChanged))
#End Region
#Region " BackDataTemplate Property Changed CallBack "
Private Shared Sub OnBackDataTemplatePropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If e.OldValue Is Nothing AndAlso e.NewValue Is Nothing Then Exit Sub
If e.OldValue IsNot Nothing AndAlso e.OldValue.Equals(e.NewValue) Then Exit Sub
Dim source As FlipPanel = TryCast(d, FlipPanel)
If source Is Nothing Then Throw New ArgumentException("source is not an instance of FlipPanel!")
' Provide any other validation here.
' Apply any other changes here.
End Sub
#End Region
#End Region
#Region " SideProperty Dependency Property "
#Region " Side Property "
Public Property Side() As Sides
Get
Return DirectCast(GetValue(SideProperty), Sides)
End Get
Set(ByVal value As Sides)
SetValue(SideProperty, value)
End Set
End Property
#End Region
#Region " Side Dependency Property "
Public Shared ReadOnly SideProperty As DependencyProperty = DependencyProperty.Register("Side", GetType(Sides), GetType(FlipPanel), New PropertyMetadata(Sides.Front, AddressOf OnSidePropertyChanged))
#End Region
#Region " Side Property Changed CallBack "
Private Shared Sub OnSidePropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If e.OldValue Is Nothing AndAlso e.NewValue Is Nothing Then Exit Sub
If e.OldValue IsNot Nothing AndAlso e.OldValue.Equals(e.NewValue) Then Exit Sub
Dim source As FlipPanel = TryCast(d, FlipPanel)
If source Is Nothing Then Throw New ArgumentException("source is not an instance of FlipPanel!")
' Provide any other validation here.
' Apply any other changes here.
End Sub
#End Region
#End Region
End Class
Now, what you've been waiting for, the code!
Enjoy!
Google Code - http://code.google.com/p/stackoverflow-answers-by-scott/
Google Code - Source Code (Zip)
Thank you!
You set the DisplayMemberPath and the SelectedValuePath to "Name", so I assume that you have a class PhoneBookEntry with a public property Name.
Have you set the DataContext to your ConnectionViewModel object?
I copied you code and made some minor modifications, and it seems to work fine.
I can set the viewmodels PhoneBookEnty property and the selected item in the combobox changes, and I can change the selected item in the combobox and the view models PhoneBookEntry property is set correctly.
Here is my XAML content:
<Window x:Class="WpfApplication6.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>
<StackPanel>
<Button Click="Button_Click">asdf</Button>
<ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding Path=PhonebookEntry}" />
</StackPanel>
</Grid>
</Window>
And here is my code-behind:
namespace WpfApplication6
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
ConnectionViewModel vm = new ConnectionViewModel();
DataContext = vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
((ConnectionViewModel)DataContext).PhonebookEntry = "test";
}
}
public class PhoneBookEntry
{
public string Name { get; set; }
public PhoneBookEntry(string name)
{
Name = name;
}
public override string ToString()
{
return Name;
}
}
public class ConnectionViewModel : INotifyPropertyChanged
{
public ConnectionViewModel()
{
IList<PhoneBookEntry> list = new List<PhoneBookEntry>();
list.Add(new PhoneBookEntry("test"));
list.Add(new PhoneBookEntry("test2"));
_phonebookEntries = new CollectionView(list);
}
private readonly CollectionView _phonebookEntries;
private string _phonebookEntry;
public CollectionView PhonebookEntries
{
get { return _phonebookEntries; }
}
public string PhonebookEntry
{
get { return _phonebookEntry; }
set
{
if (_phonebookEntry == value) return;
_phonebookEntry = value;
OnPropertyChanged("PhonebookEntry");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Edit: Geoffs second example does not seem to work, which seems a bit odd to me. If I change the PhonebookEntries property on the ConnectionViewModel to be of type ReadOnlyCollection, the TwoWay binding of the SelectedValue property on the combobox works fine.
Maybe there is an issue with the CollectionView? I noticed a warning in the output console:
System.Windows.Data Warning: 50 : Using CollectionView directly is not fully supported. The basic features work, although with some inefficiencies, but advanced features may encounter known bugs. Consider using a derived class to avoid these problems.
Edit2 (.NET 4.5): The content of the DropDownList can be based on ToString() and not of DisplayMemberPath, while DisplayMemberPath specifies the member for the selected and displayed item only.
Best Solution
The debugger output actually gives you a hint to the problem:
System.Windows.Data Error: BindingExpression path error: 'SelectedItem' property not found on 'ExpressionElements.SomeClass' 'ExpressionElements.SomeClass' (HashCode=49044892). BindingExpression: Path='SelectedItem' DataItem='ExpressionElements.SomeClass' (HashCode=49044892); target element is 'System.Windows.Controls.ComboBox' (Name=''); target property is 'SelectedItem' (type 'System.Object')..
Because the Data context for the template is an instance of the SomeClass class, all you have to do is change the SelectedItem binding from SelectedItem to StringsChild.SelectedItem: