I'm in search of some help. I've created a very basic MVVM setup. My object is called VNode which has the properties Name,Age,Kids. What I want to happen is when the user selects VNodes on the left, it displays their more in depth data on the right as scene in the image below. I'm not sure how to go about doing this.
image 1: Current
Image 2: Goal
If you don't feel like using the code below to recreate the window you can grab the project solution files from here: DropboxFiles
VNode.cs
namespace WpfApplication1
{
public class VNode
{
public string Name { get; set; }
public int Age { get; set; }
public int Kids { get; set; }
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="8" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" Background="AliceBlue" ItemsSource="{Binding VNodes}" SelectionMode="Extended">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Name: " />
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
<ListBox Grid.Column="2" Background="LightBlue" ItemsSource="{Binding VNodes}">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text=":" FontWeight="Bold" />
<TextBlock Text=" age:"/>
<TextBlock Text="{Binding Age}" FontWeight="Bold" />
<TextBlock Text=" kids:"/>
<TextBlock Text="{Binding Kids}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
MainViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApplication1
{
public class MainViewModel : ObservableObject
{
private ObservableCollection<VNode> _vnodes;
public ObservableCollection<VNode> VNodes
{
get { return _vnodes; }
set
{
_vnodes = value;
NotifyPropertyChanged("VNodes");
}
}
Random r = new Random();
public MainViewModel()
{
//hard coded data for testing
VNodes = new ObservableCollection<VNode>();
List<string> names = new List<string>() { "Tammy", "Doug", "Jeff", "Greg", "Kris", "Mike", "Joey", "Leslie", "Emily","Tom" };
List<int> ages = new List<int>() { 32, 24, 42, 57, 17, 73, 12, 8, 29, 31 };
for (int i = 0; i < 10; i++)
{
VNode item = new VNode();
int x = r.Next(0,9);
item.Name = names[x];
item.Age = ages[x];
item.Kids = r.Next(1, 5);
VNodes.Add(item);
}
}
}
}
ObservableObject.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApplication1
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
UPDATED
For the sake of example, how about demonstrating if the user just selects a single item in the ListBox on the right, it then displays that selected items more in-depth data on the right as shown in the image below?
There are three and a half answers here. Number one is good general WPF practice that doesn't work in the specific case of ListBox. The second one is a quick and dirty workaround for the problem with ListBox, and the last is the best, because it does nothing in code behind. Least code behind is best code behind.
The first way to do this doesn't require anything of the items you're displaying in the ListBox. They could be strings or integers. If your item type (or types) is a class (or are classes) with a little more meat to it, and you'd like to have each instance know whether it's been selected or not, we'll get to that next.
You need to give your view model another ObservableCollection<VNode> called SelectedVNodes or some such.
private ObservableCollection<VNode> _selectedvnodes;
public ObservableCollection<VNode> SelectedVNodes
{
get { return _selectedvnodes; }
set
{
_selectedvnodes = value;
NotifyPropertyChanged("SelectedVNodes");
}
}
public MainViewModel()
{
VNodes = new ObservableCollection<VNode>();
SelectedVNodes = new ObservableCollection<VNode>();
// ...etc., just as you have it now.
If System.Windows.Controls.ListBox weren't broken, then in your first ListBox, you would bind SelectedItems to that viewmodel property:
<ListBox
Grid.Column="0"
Background="AliceBlue"
ItemsSource="{Binding VNodes}"
SelectedItems="{Binding SelectedVNodes}"
SelectionMode="Extended">
And the control would be in charge of the content of SelectedVNodes. You could also change SelectedVNodes programmatically, and that would update both lists.
But System.Windows.Controls.ListBox is broken, and you can't bind anything to SelectedItems. The simplest workaround is to handle the ListBox's SelectionChanged event and kludge it in the code behind:
XAML:
<ListBox
Grid.Column="0"
Background="AliceBlue"
ItemsSource="{Binding VNodes}"
SelectionMode="Extended"
SelectionChanged="ListBox_SelectionChanged">
C#:
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox lb = sender as ListBox;
MainViewModel vm = DataContext as MainViewModel;
vm.SelectedVNodes.Clear();
foreach (VNode item in lb.SelectedItems)
{
vm.SelectedVNodes.Add(item);
}
}
Then bind ItemsSource in your second ListBox to SelectedVNodes:
<ListBox
Grid.Column="2"
Background="LightBlue"
ItemsSource="{Binding SelectedVNodes}">
And that should do what you want. If you want to be able to update SelectedVNodes programmatically and have the changes reflected in both lists, you'll have to have your codebehind class handle the PropertyChanged event on the viewmodel (set that up in the codebehind's DataContextChanged event), and the CollectionChanged event on viewmodel.SelectedVNodes -- and remember to set the CollectionChanged handler all over again every time SelectedVNodes changes its own value. It gets ugly.
A better long-term solution would be to write an attachment property for ListBox that replaces SelectedItems and works right. But this kludge will at least get you moving for the time being.
Update
Here's a second way of doing it, which OP suggested. Instead of maintaining a selected item collection, we put a flag on each item, and the viewmodel has a filtered version of the main item list that returns only selected items. I'm drawing a blank on how to bind VNode.IsSelected to the IsSelected property on ListBoxItem, so I just did that in the code behind.
VNode.cs:
using System;
namespace WpfApplication1
{
public class VNode
{
public string Name { get; set; }
public int Age { get; set; }
public int Kids { get; set; }
// A more beautiful way to do this would be to write an IVNodeParent
// interface with a single method that its children would call
// when their IsSelected property changed -- thus parents would
// implement that, and they could name their "selected children"
// collection properties anything they like.
public ObservableObject Parent { get; set; }
private bool _isSelected = false;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
if (null == Parent)
{
throw new NullReferenceException("VNode.Parent must not be null");
}
Parent.NotifyPropertyChanged("SelectedVNodes");
}
}
}
}
}
MainViewModel.cs:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApplication1
{
public class MainViewModel : ObservableObject
{
private ObservableCollection<VNode> _vnodes;
public ObservableCollection<VNode> VNodes
{
get { return _vnodes; }
set
{
_vnodes = value;
NotifyPropertyChanged("VNodes");
NotifyPropertyChanged("SelectedVNodes");
}
}
public IEnumerable<VNode> SelectedVNodes
{
get { return _vnodes.Where(vn => vn.IsSelected); }
}
Random r = new Random();
public MainViewModel()
{
//hard coded data for testing
VNodes = new ObservableCollection<VNode>();
List<string> names = new List<string>() { "Tammy", "Doug", "Jeff", "Greg", "Kris", "Mike", "Joey", "Leslie", "Emily","Tom" };
List<int> ages = new List<int>() { 32, 24, 42, 57, 17, 73, 12, 8, 29, 31 };
for (int i = 0; i < 10; i++)
{
VNode item = new VNode();
int x = r.Next(0,9);
item.Name = names[x];
item.Age = ages[x];
item.Kids = r.Next(1, 5);
item.Parent = this;
VNodes.Add(item);
}
}
}
}
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (VNode item in e.RemovedItems)
{
item.IsSelected = false;
}
foreach (VNode item in e.AddedItems)
{
item.IsSelected = true;
}
}
}
}
MainWindow.xaml (partial):
<ListBox
Grid.Column="0"
Background="AliceBlue"
ItemsSource="{Binding VNodes}"
SelectionMode="Extended"
SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Name: " />
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
<ListBox Grid.Column="2" Background="LightBlue" ItemsSource="{Binding SelectedVNodes}">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text=":" FontWeight="Bold" />
<TextBlock Text=" age:"/>
<TextBlock Text="{Binding Age}" FontWeight="Bold" />
<TextBlock Text=" kids:"/>
<TextBlock Text="{Binding Kids}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Update 2
And here, finally, is how you do it with binding (thanks to OP for figuring out for me how to bind data item properties to ListBoxItem properties -- I should be able to accept his comment as an answer!):
In MainWindow.xaml, get rid of the SelectionCanged event (yay!), and set a Style to do the binding only on the items in the first ListBox. In the second ListBox, that binding will create problems which I'll leave to somebody else to resolve; I have a guess that it might be fixable by fiddling with the order of notifications and assignments in VNode.IsSelected.set, but I could be wildly wrong about that. Anyway the binding serves no purpose in the second ListBox so there's no reason to have it there.
<ListBox
Grid.Column="0"
Background="AliceBlue"
ItemsSource="{Binding VNodes}"
SelectionMode="Extended"
>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Name: " />
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
...and I removed the event handler method from the codebehind. But you didn't add it at all, because you're smarter than me and you started with this last version of the answer.
In VNode.cs, VNode becomes an ObservableObject so he can advertise his selection status, and he also fires the appropriate notification in IsSelected.set. He still has to fire the change notification for his Parent's SelectedVNodes property, because the second listbox (or any other consumer of SelectedVNodes) needs to know that the set of selected VNodes has changed.
Another way to do that would be to make SelectedVNodes an ObservableCollection again, and have VNode add/remove himself from it when his selected status changes. Then the viewmodel would have to handle CollectionChanged events on that collection, and update the VNode IsSelected properties when they're added to it or removed from it. If you do that, it's very important to keep the if in VNode.IsSelected.set, to prevent infinite recursion.
using System;
namespace WpfApplication1
{
public class VNode : ObservableObject
{
public string Name { get; set; }
public int Age { get; set; }
public int Kids { get; set; }
public ObservableObject Parent { get; set; }
private bool _isSelected = false;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
if (null == Parent)
{
throw new NullReferenceException("VNode.Parent must not be null");
}
Parent.NotifyPropertyChanged("SelectedVNodes");
NotifyPropertyChanged("IsSelected");
}
}
}
}
}
Update 3
OP asks about displaying a single selection in a detail pane. I left the old multi-detail pane in place to demonstrate sharing a template.
That's pretty simple to do, so I elaborated a bit. You could do this only in the XAML, but I threw in a SelectedVNode property in the viewmodel to demonstrate that as well. It's not used for anything, but if you wanted to throw in a command that operated on the selected item (for example), that's how the view model would know which item the user means.
MainViewModel.cs
// Add to MainViewModle class
private VNode _selectedVNode = null;
public VNode SelectedVNode
{
get { return _selectedVNode; }
set
{
if (value != _selectedVNode)
{
_selectedVNode = value;
NotifyPropertyChanged("SelectedVNode");
}
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<SolidColorBrush x:Key="ListBackgroundBrush" Color="Ivory" />
<DataTemplate x:Key="VNodeCardTemplate">
<Grid>
<Border
x:Name="BackgroundBorder"
BorderThickness="1"
BorderBrush="Silver"
CornerRadius="16,6,6,6"
Background="White"
Padding="6"
Margin="4,4,8,8"
>
<Border.Effect>
<DropShadowEffect BlurRadius="2" Opacity="0.25" ShadowDepth="4" />
</Border.Effect>
<Grid
x:Name="ContentGrid"
>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<!-- Each gets half of what's left -->
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="0.5*" />
</Grid.ColumnDefinitions>
<Border
Grid.Row="0" Grid.RowSpan="3"
VerticalAlignment="Top"
Grid.Column="0"
BorderBrush="{Binding Path=BorderBrush, ElementName=BackgroundBorder}"
BorderThickness="1"
CornerRadius="9,4,4,4"
Margin="2,2,6,2"
Padding="4"
>
<StackPanel Orientation="Vertical">
<StackPanel.Effect>
<DropShadowEffect BlurRadius="2" Opacity="0.25" ShadowDepth="2" />
</StackPanel.Effect>
<Ellipse
Width="16" Height="16"
Fill="DarkOliveGreen"
Margin="0,0,0,2"
HorizontalAlignment="Center"
/>
<Border
CornerRadius="6,6,2,2"
Background="DarkOliveGreen"
Width="36"
Height="18"
Margin="0"
/>
</StackPanel>
</Border>
<TextBlock Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding Name}" FontWeight="Bold" />
<Separator Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Background="{Binding Path=BorderBrush, ElementName=BackgroundBorder}" Margin="0,3,0,3" />
<!--
Mode=OneWay on Run.Text because bindings on that property should default to that, but don't.
And if you bind TwoWay to a property without a setter, it throws an exception.
-->
<TextBlock Grid.Row="2" Grid.Column="1"><Bold>Age:</Bold> <Run Text="{Binding Age, Mode=OneWay}" /></TextBlock>
<TextBlock Grid.Row="2" Grid.Column="2"><Bold>Kids:</Bold> <Run Text="{Binding Kids, Mode=OneWay}" /></TextBlock>
</Grid>
</Border>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter TargetName="ContentGrid" Property="Visibility" Value="Hidden" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<!-- I think this should be the default, but it isn't. -->
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</Window.Resources>
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="8" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.5*" />
<RowDefinition Height="0.5*" />
</Grid.RowDefinitions>
<ListBox
x:Name="VNodeMasterList"
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
Background="{StaticResource ListBackgroundBrush}"
ItemsSource="{Binding VNodes}"
SelectionMode="Extended"
SelectedItem="{Binding SelectedVNode}"
>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Name: " />
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<GridSplitter Grid.Column="1" Grid.RowSpan="2" Grid.Row="0" Width="5" HorizontalAlignment="Stretch" />
<Border
Grid.Column="2"
Grid.Row="0"
Background="{StaticResource ListBackgroundBrush}"
>
<ContentControl
Content="{Binding ElementName=VNodeMasterList, Path=SelectedItem}"
ContentTemplate="{StaticResource VNodeCardTemplate}"
/>
</Border>
<ListBox
Grid.Column="2"
Grid.Row="1"
Background="{StaticResource ListBackgroundBrush}"
ItemsSource="{Binding SelectedVNodes}"
ItemTemplate="{StaticResource VNodeCardTemplate}"
/>
</Grid>
</Window>
Related
I'm trying to create a sort of ShellView without using thirdparty frameworks, and I'm doing trying to do it via. ContentControls and UserControls.
I can navigate/switch the UserControls that is showed inside the ContentControls as long as the commands are fired from outside the UserControls, but nothing is happening when I move the code inside a button in a UserControl.
Currently I'm having one MainWindow.XAML, where I have one ContentControl.
This ContentControl is hosting either a LoginWindowUserControl, or a UserWindowUserControl - I want to be able switch the UserControl from within one of the UserControls.
My MainWindow.XAML looks like this:
<Window x:Class="ModelHealthApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vms="clr-namespace:ModelHealthApplication.ViewModels"
xmlns:ia="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:views="clr-namespace:ModelHealthApplication.Views.UserControls"
xmlns:local="clr-namespace:ModelHealthApplication"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type vms:CurrentWindowUserStateViewModel}">
<views:UserView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:LoginWindowViewModel}">
<views:LoginView />
</DataTemplate>
<vms:NavigationViewModel x:Key="nVm" />
</Window.Resources>
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Loaded">
<ia:InvokeCommandAction Command="{Binding Source={StaticResource nVm}, Path=OpenLoginWindowCommand}" />
</ia:EventTrigger>
</ia:Interaction.Triggers>
<Grid DataContext="{StaticResource nVm}">
<DockPanel>
<Button Content="Test" DockPanel.Dock="Left" Command="{Binding OpenUserWindowStateCommand}" />
<ContentControl x:Name="WindowUserState" Content="{Binding CurrentWindowUserState}" />
</DockPanel>
</Grid>
And my UserWindowUserControl looks like this:
<UserControl x:Class="ModelHealthApplication.Views.UserControls.UserView"
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:vms="clr-namespace:ModelHealthApplication.ViewModels"
xmlns:views="clr-namespace:ModelHealthApplication.Views.UserControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ModelHealthApplication.Views.UserControls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type vms:MyModelsViewModel}">
<views:MyModelsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vms:MyAccountViewModel}">
<views:MyAccountView />
</DataTemplate>
<vms:NavigationViewModel x:Key="nVm" />
</UserControl.Resources>
<Grid>
<DockPanel DataContext="{StaticResource nVm}">
<Grid DockPanel.Dock="Top" Background="{StaticResource MainBlue}" Height="25">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0">
<Button Content="My Models"
HorizontalAlignment="Center"
Command="{Binding OpenMyModelsCommand}"
Style="{StaticResource NavButtonStyle}"/>
<Button Content="My Account"
HorizontalAlignment="Center"
Command="{Binding OpenMyAccountCommand}"
Style="{StaticResource NavButtonStyle}"
/>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right" Grid.Column="1" Margin="0, 0, 10, 0">
<TextBlock VerticalAlignment="Center" Foreground="White">
<Run Text="Logged in as:" FontWeight="Bold"/>
<Run Text="{Binding LoggedInAs}" d:Text="TestUser" />
</TextBlock>
<TextBlock Margin="20, 0 ,0 ,0" Text="Log Out" VerticalAlignment="Center" TextDecorations="Underline" Foreground="{StaticResource ComplenetarySecondOrange}" Background="{DynamicResource MainBlue}" Cursor="Hand">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding OpenLoginWindowCommand}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
</StackPanel>
</Grid>
<ContentControl x:Name="Pages" Content="{Binding SelectedViewModel}" />
</DockPanel>
</Grid>
</UserControl>
When I press the "Log Out" button/textblock I want to navigate back to the login window, but I can only do this from buttons that exist outside the UserControl.
I've read several other post similar to this, but I haven't found a solution that fits my needs - I tried using RelativeSource but without success - maybe someone can see what I'm doing wrong.
I'm using a "NavigationViewModel" to hold the commands which as I mentioned, works fine outside the UserControls:
using ModelHealthApplication.Commands;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace ModelHealthApplication.ViewModels
{
public class NavigationViewModel : INotifyPropertyChanged
{
public ICommand OpenMyModelsCommand { get; set; }
public ICommand OpenMyAccountCommand { get; set; }
public ICommand OpenUserWindowStateCommand { get; set; }
public ICommand OpenLoginWindowCommand { get; set; }
private object currentWindowUserState;
public object CurrentWindowUserState
{
get { return currentWindowUserState; }
set
{
currentWindowUserState = value;
OnPropertyChanged("CurrentWindowUserState");
}
}
private object selectedViewModel;
public object SelectedViewModel
{
get { return selectedViewModel; }
set
{
selectedViewModel = value;
OnPropertyChanged("SelectedViewModel");
}
}
public NavigationViewModel()
{
OpenMyModelsCommand = new OpenMyModelsCommand(this);
OpenMyAccountCommand = new OpenAccountCommand(this);
OpenUserWindowStateCommand = new OpenUserWindowStateCommand(this);
OpenLoginWindowCommand = new OpenLoginWindowCommand(this);
}
public void OpenUserWindowState(object obj)
{
CurrentWindowUserState = new CurrentWindowUserStateViewModel();
}
public void OpenLoginWindow(object obj)
{
CurrentWindowUserState = new LoginWindowViewModel();
}
public void OpenMyModels(object obj)
{
SelectedViewModel = new MyModelsViewModel();
}
public void OpenMyAccount(object obj)
{
SelectedViewModel = new MyAccountViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
}
Ended up solving this with a great tutorial from SingletonSean on Youtube.
Posting here if anyone comes across this, and has the same issue.
Singleton Sean MVVM Navigatoin
I am using Prism and WPF. Here is my ViewModel:
using DialogueTool.Service.Abstracts;
using DialogueTool.Service.Models.Concretes;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace DialogueTool.UI.ViewModels
{
public class DialogueEntryControlViewModel : BindableBase, INavigationAware
{
private IServices services;
private DialogueWrapperModel dialogueWrapperModel;
public DialogueEntryControlViewModel(IServices _services)
{
services = _services;
}
DialogueWrapperModel DialogueWrapperModel { get; set; }
public void OnNavigatedTo(NavigationContext navigationContext)
{
dialogueWrapperModel = navigationContext.Parameters.GetValue<DialogueWrapperModel>("DialogueWrapperModel");
DialogueEntries = new ObservableCollection<DialogueEntryModel>(dialogueWrapperModel.DialogueEntries);
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
//throw new NotImplementedException();
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
//throw new NotImplementedException();
}
#region Properties
private ObservableCollection<DialogueEntryModel> _breadCrumbs = new ObservableCollection<DialogueEntryModel>();
public ObservableCollection<DialogueEntryModel> BreadCrumbs
{
get { return _breadCrumbs; }
set { SetProperty(ref _breadCrumbs, value); }
}
/// <summary>
///
/// </summary>
public ObservableCollection<DialogueEntryModel> _dialogueEntries = new ObservableCollection<DialogueEntryModel>();
public ObservableCollection<DialogueEntryModel> DialogueEntries
{
get { return _dialogueEntries; }
set{ SetProperty(ref _dialogueEntries, value); }
}
private DialogueEntryModel _selectedDialogueEntry;
public DialogueEntryModel SelectedDialogueEntry
{
get { return _selectedDialogueEntry; }
set { SetProperty(ref _selectedDialogueEntry, value); }
}
private DialogueEntryModel _selectedDialogueEntryChoice;
public DialogueEntryModel SelectedDialogueEntryChoice
{
get { return _selectedDialogueEntryChoice; }
set {
SetProperty(ref _selectedDialogueEntryChoice, value);
BreadCrumbs.Add(value);
}
}
#endregion
#region Commands
#endregion
}
}
Here is my xaml:
<UserControl x:Class="DialogueTool.UI.Views.Controls.DialogueEntryControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:templateSelector="clr-namespace:DialogueTool.UI.DataTemplateSelectors">
<UserControl.Resources>
<DataTemplate x:Key="QuestionDataTemplateTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0"
Grid.Row="0"
TextWrapping="Wrap"
VerticalAlignment="Center"
Text="{Binding Text, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBlock>
<TextBlock Grid.Column="1"
Grid.Row="0"
FontSize="25"
FontWeight="Bold"
VerticalAlignment="Center">⇩</TextBlock>
<TreeView Grid.ColumnSpan="2"
Grid.Column="0"
Grid.Row="1"
Visibility="{Binding Path=DialogueEntries, Converter={StaticResource EmptyListToVisibilityConverter} }">
<TreeViewItem Header="Choices"
IsExpanded="{Binding Path=DialogueEntries, Converter={StaticResource EmptyListToBoolConverter} }">
<StackPanel Width="160">
<!--<Label>Search</Label>
<TextBox Text="{Binding ChapterSearchText, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />-->
<!---->
<Label>Choices</Label>
<ListBox ItemsSource="{Binding DialogueEntries}"
SelectedItem="{Binding Path=SelectedDialogueEntryChoice, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="146">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0"
Grid.Row="0"
MaxWidth="150"
TextWrapping="Wrap"
VerticalAlignment="Center"
Text="{Binding Text, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBlock>
<TextBlock Grid.Column="1"
Grid.Row="0"
FontSize="25"
FontWeight="Bold"
VerticalAlignment="Center">⇨</TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</TreeViewItem>
</TreeView>
</Grid>
</DataTemplate>
<DataTemplate x:Key="TextDataTemplateTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0"
Grid.Row="0"
TextWrapping="Wrap"
VerticalAlignment="Center"
Text="{Binding Text, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBlock>
<TextBlock Grid.Column="1"
Grid.Row="0"
FontSize="25"
FontWeight="Bold"
VerticalAlignment="Center">⇩</TextBlock>
</Grid>
</DataTemplate>
<templateSelector:DialogueEntryDataTemplateSelector x:Key="DialogueEntryDataTemplateSelector"
x:Name="DialogueEntryDataTemplateSelector"
TextTemplate="{StaticResource TextDataTemplateTemplate}"
QuestionTemplate="{StaticResource QuestionDataTemplateTemplate}" />
</UserControl.Resources>
<StackPanel>
<StackPanel FlowDirection="LeftToRight"
Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding BreadCrumbs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Excerpt}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!---->
<Label>Dialogue Entries</Label>
<ListBox ItemsSource="{Binding DialogueEntries}"
SelectedItem="{Binding SelectedDialogueEntry, Mode=TwoWay}"
ItemTemplateSelector="{StaticResource DialogueEntryDataTemplateSelector}">
</ListBox>
</StackPanel>
</UserControl>
The following binding to SelectedDialogueEntryChoice is not triggering my ViewModel's SelectedDialogueEntryChoice property:
<ListBox ItemsSource="{Binding DialogueEntries}"
SelectedItem="{Binding Path=SelectedDialogueEntryChoice, Mode=TwoWay}">
I've read that it may be that the Tree is absorbing the click event but I haven't yet figured out how to ensure the selected item event works
Just to ensure I don't miss anything off that may be of importance, here is the DataTemplateSelector class I am using:
using DialogueTool.Domain.Enums;
using DialogueTool.Service.Models.Concretes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace DialogueTool.UI.DataTemplateSelectors
{
public class DialogueEntryDataTemplateSelector : DataTemplateSelector
{
public DataTemplate QuestionTemplate { get; set; }
public DataTemplate TextTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var dialogueEntryModel = item as DialogueEntryModel;
switch (dialogueEntryModel.Type)
{
case DialogueEntryType.Question:
return QuestionTemplate;
case DialogueEntryType.Text:
return TextTemplate;
default:
return TextTemplate;
}
}
}
}
Responses to questions in comments:
Q: What does "does not trigger the property" mean?
A: The following set method is not triggered when the selected item is changed.
private DialogueEntryModel _selectedDialogueEntryChoice;
public DialogueEntryModel SelectedDialogueEntryChoice
{
get { return _selectedDialogueEntryChoice; }
set {
SetProperty(ref _selectedDialogueEntryChoice, value);
BreadCrumbs.Add(value);
}
}
As an example, the SelectedDialogueEntry set method does get fired
Things i'm trying:
I have tried to Bind to the UserControl element of my Xaml which I was hoping has access to the DataContext and thus my ViewModel... But to no avail:
<ListBox ItemsSource="{Binding DialogueEntries}"
SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=SelectedDialogueEntryChoice}">
Things i've found out:
If I take the code out of the template selector, the SelectedDialogueEntryChoice works. So I am assuming it's to do with it losing access to the ViewModel
The trick was to bind directly to DataContext. I also needed to find AncestorType of UserControl which the DataContext is bound to:
<ListBox ItemsSource="{Binding DialogueEntries}"
SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.SelectedDialogueEntryChoice, Mode=TwoWay}">
I'm trying to access radio buttons inside datatemplate which is in a datagrid. I came across a lot of answers but none work for me.
I have 3 different radio buttions in 3 different columns. i just cant figure out how to access those radio buttons and its killing me, i'v been struggling with it for days PLEASE HELP.
<DataGridTemplateColumn Header="Yes" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Opacity="0.8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<RadioButton x:Name="rbYes" GroupName="{Binding QuestionID}" Content="Yes" Grid.Column="0" />
</Grid>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="No" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Opacity="0.8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<RadioButton x:Name="rbNo" GroupName="{Binding QuestionID}" Content="No" Grid.Column="0" />
</Grid>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="N/A" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Opacity="0.8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<RadioButton x:Name="rbNA" GroupName="{Binding QuestionID}" Content="N/A" Grid.Column="0" />
</Grid>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
And here is my code behind. I have changed it so many times.
RadioButton rbYes = (RadioButton)FindName("rbYes");
RadioButton rbNo = (RadioButton)FindName("rbNo");
RadioButton rbNA = (RadioButton)FindName("rbNA");
if (rbYes.IsChecked == true)
{
rbNo.IsChecked = false;
rbNA.IsChecked = false;
int questionID = Convert.ToInt32(drv["QuestionID"]);
questionAnswer = "Yes".ToString();
if (drv["Notes"].ToString() == "")
{
string notes = null;
}
else
{
string notes = drv["Notes"].ToString();
}
Assuming that you are familiar with data binding (as you have already bound the QuestionID) my suggestion is as follows;
Have 3 boolean properties say IsYes, IsNo, IsNA in the object through which the data is populated in the datagrid. Make the collection that is bound to the ItemSource of your datagrid as an ObservableCollection.
Bind the IsChecked of the radio buttons to these three properties respectively.
By doing the above steps, you don't have to worry about going deep into the cells of your datagrid to find the controls and to know whether they are set or not.
[UPDATE] Sorry for the delay. I was in a meeting. The Code is as follows.
Please note that this is a very rough mock up of how to achieve the concept which I mentioned above.
MainWindow.xaml
<Window x:Class="testWPFApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:testWPFApplication"
mc:Ignorable="d" DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="LayoutRoot" >
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<DataGrid Grid.Row="0" SelectionMode="Single"
ItemsSource="{Binding Questions}" SelectedItem="{Binding SelectedQuestion,Mode=TwoWay}" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserDeleteRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Question Id" Binding="{Binding QuestionId}" IsReadOnly="True" />
<DataGridTextColumn Header="Question" Binding="{Binding QuestionDescription}" Width="3*" IsReadOnly="True" />
<DataGridTemplateColumn Header="Yes">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<RadioButton GroupName="{Binding QuestionId}" IsChecked="{Binding IsYes, Mode=TwoWay,UpdateSourceTrigger=LostFocus,NotifyOnTargetUpdated=True}"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="No">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<RadioButton GroupName="{Binding QuestionId}" IsChecked="{Binding IsNo, Mode=TwoWay,UpdateSourceTrigger=LostFocus,NotifyOnTargetUpdated=True}"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="N/A">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<RadioButton GroupName="{Binding QuestionId}" IsChecked="{Binding IsNA, Mode=TwoWay,UpdateSourceTrigger=LostFocus,NotifyOnTargetUpdated=True}"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Grid.Row="1" Width="100" x:Name="btnCheckAnswers" HorizontalAlignment="Right" Margin="5" Click="btnCheckAnswers_Click" Content="Check Answers" />
</Grid>
</Window>
MainWindow.xaml.cs (code behind. can be moved a different ViewModel class)
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace testWPFApplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Question> _questions;
private Question _selectedQuestion;
public ObservableCollection<Question> Questions
{
get { return _questions; }
set
{
_questions = value;
OnPropertyChanged("Questions");
}
}
public Question SelectedQuestion
{
get { return _selectedQuestion; }
set
{
_selectedQuestion = value;
OnPropertyChanged("SelectedQuestion");
OnSelectedQuestionChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string strPropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(strPropertyName));
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
Init();
}
private void Init()
{
CreateMockData();
}
private void CreateMockData()
{
if (Questions == null)
{
Questions = new ObservableCollection<Question>();
}
else
{
Questions.Clear();
}
for (int i = 1; i <= 5; i++)
{
Question q = new Question
{
QuestionId = i,
QuestionDescription = "Sample Question " + i.ToString(),
IsYes = false,
IsNA = false,
IsNo = false
};
Questions.Add(q);
}
}
private void OnSelectedQuestionChanged()
{
if(SelectedQuestion != null)
{
}
}
private void btnCheckAnswers_Click(object sender, RoutedEventArgs e)
{
StringBuilder strQuestionAnswer = new StringBuilder();
foreach (Question item in Questions)
{
strQuestionAnswer.AppendLine(item.QuestionDescription + ":");
strQuestionAnswer.AppendLine("IsYes:" + item.IsYes);
strQuestionAnswer.AppendLine("IsNo:" + item.IsNo);
strQuestionAnswer.AppendLine("IsNA:" + item.IsNA);
}
MessageBox.Show(strQuestionAnswer.ToString());
}
}
public class Question
{
public int QuestionId { get; set; }
public string QuestionDescription { get; set; }
public bool IsYes { get; set; }
public bool IsNo { get; set; }
public bool IsNA { get; set; }
}
}
Window Load
Few values set
Result when the button is clicked
Hope this example helps you to address the issue that you have.
Thank you for answering so quickly. But thats not the problem, the problem is, I'm try to access the radiobuttons im using but i can find the buttons with the FindName function. Null is returned for rbYes, rbNo and rbNA. I don't know if my explanation is good enough.
I am implementing a chat application like this image:
I started by creating a ListBox and setting a ListBox.ItemTemplate, but i can't figure out how can i control the ListBox to add an item with the layout as a message received or as send (just like Whatsapp).
Here is my code:
<ListBox Name="ChatListBox" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Background="#00FFFFFF" BorderBrush="{x:Null}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Background="#00FFFFFF"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<!--If the user sends a msg-->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="9*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Margin="0" BorderThickness="1" BorderBrush="#9f9f9f" Background="#c4df9b" CornerRadius="10">
<TextBlock Name="MsgText" Background="#c4df9b" Foreground="Black" TextAlignment="Center" TextWrapping="Wrap" Margin="5" Text="{Binding text}" FontSize="14"/>
</Border>
<Image Grid.Column="1" Source="Images/user.png" Margin="5" Height="{Binding ElementName=MsgText, Path=ActualHeight}"/>
<!--
If the user receives a msg
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="9*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="Images/user.png" Margin="5" Height="{Binding ElementName=MsgText, Path=ActualHeight}"/>
<Border Grid.Column="1" Margin="0" BorderThickness="1" BorderBrush="#9f9f9f" Background="#c4df9b" CornerRadius="10">
<TextBlock Name="MsgText" Background="#c4df9b" Foreground="Black" TextAlignment="Center" TextWrapping="Wrap" Margin="5" Text="{Binding text}" FontSize="14"/>
</Border>-->
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here is my c# code:
List<ChatItem> chatItem = new List<ChatItem>();
chatItem.Add(new ChatItem() { text = "Hello...", isFromUser = false });
chatItem.Add(new ChatItem() { text = "hi!", isFromUser = true });
chatItem.Add(new ChatItem() { text = "this is a test, this is a test, this is a test, this is a test, this is a test, this is a test, this is a test, this is a test, this is a test, this is a test", isFromUser = false });
ChatListBox.ItemsSource = chatItem;
This is how the ListBox is redered:
Is there any way of adding an IF statement at the ListBox with WPF? or how can i control which ListBox.ItemTemplate to add.
If your messages are of the same class you could use itemsTemplateSelector as in http://codingbandit.com/blog/?p=8
if there are different classes you should just use the datatemplate datatype property as in Conditional List itemtemplate or datatemplate in WPF
Another option is to simply use a data trigger:
<DataTemplate x:Key="ToTemplate">
... etc ...
</DataTemplate>
<DataTemplate x:Key="FromTemplate">
... etc ...
</DataTemplate>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding isFromUser}" Value="false">
<Setter Property="Template" Value="{StaticResource ToTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding isFromUser}" Value="true">
<Setter Property="Template" Value="{StaticResource FromTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
Maybe this will give you an idea
Xaml
<Window x:Class="Q1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Q1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="intDataTemplate">
<TextBox Text="{Binding Path=.}" Width="80"/>
</DataTemplate>
<DataTemplate x:Key="stringDataTemplate">
<TextBlock Text="{Binding Path=.}"/>
</DataTemplate>
<local:MyDataTemplateSelector IntDataTemplate="{StaticResource intDataTemplate}"
StringDataTemplate="{StaticResource stringDataTemplate}"
x:Key="myDataTemplateSelector"/>
</Window.Resources>
<Grid>
<ListBox x:Name="myListBox" ItemsSource="{Binding}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}">
</ListBox>
</Grid>
Code-Behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Q1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<System.Object> myList = new List<object>();
myList.Add(1);
myList.Add("Alpha");
myList.Add(2);
myList.Add("Beta");
myList.Add(3);
myList.Add("Gamma");
myListBox.DataContext = myList;
}
}
}
DataTemplateSelector
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Q1
{
public class MyDataTemplateSelector : System.Windows.Controls.DataTemplateSelector
{
public System.Windows.DataTemplate IntDataTemplate { get; set; }
public System.Windows.DataTemplate StringDataTemplate { get; set; }
public MyDataTemplateSelector()
{
IntDataTemplate = new System.Windows.DataTemplate();
StringDataTemplate = new System.Windows.DataTemplate();
}
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
if (item is Int32)
{
return IntDataTemplate;
}
else
{
return StringDataTemplate;
}
}
}
}
You could use DataTemplateSelector to select the Template according with your data, here's an example.
For this example, I'll use messages, a client message and a server message:
public abstract class Message
{
public string Content { get; set; }
public override string ToString()
{
return Content;
}
}
public class ServerMessage : Message
{
}
public class ClientMessage : Message
{
}
In this way, I can check the type of the object and apply certain template.
Let's define our templates and selector:
XAML:
<DataTemplate x:Key="clientTemplate" >
<TextBlock Text="{Binding Content}"
Foreground="Red"/>
</DataTemplate>
<DataTemplate x:Key="serverClient">
<TextBlock Text="{Binding Content}"
Foreground="Green"/>
</DataTemplate>
<local:MessageTemplateSelector x:Key="messageSelector"
ServerTemplate="{StaticResource serverClient}"
ClientTemplate="{StaticResource clientTemplate}"/>
DataTemplateSelector
public class MessageTemplateSelector : DataTemplateSelector
{
public DataTemplate ClientTemplate { get; set; }
public DataTemplate ServerTemplate { get; set; }
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
if (item.GetType() == typeof(ClientMessage))
return ClientTemplate;
return ServerTemplate;
}
}
First of all, as you can see I create two DataTemplate properties on my Selector, which I set it by XAML, doing this is easy to retrieve the DataTemplate, and finally I just compare the item received by parameter, which is the binded object(Message), and check its type and return the template.
And that is it, it works like expected.
UPDATE: Let's supposed that you want Template your items based on their types, just like my example, you could this the same thing above without using TemplateSelector, you could define the DataType of your Template:
<Window.Resources>
<DataTemplate DataType="{x:Type local:ClientMessage}">
<TextBlock Text="{Binding Content}"
Foreground="Red"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ServerMessage}">
<TextBlock Text="{Binding Content}"
Foreground="Green"/>
</DataTemplate>
</Window.Resources>
Doing so, the Template will be selected automatically according the type of the object.
Updated
Link to code: Dropbox files
I have a WPF listview populated with a data which is bound through itemsSource. The item bound is a class object called User which contains a handful of properties such as Name, Age, Mail and Info.
What I want to do is be able to double click any listview item and popup an edit dialog which will allow me to change the Mail and Info of that individual. Those changes however will only be committed when i hit OK otherwise they are ignored.
The problem or place where I'm stuck at is, how do i populate the Edit Dialog with the selected listview item's info/mail?
How do i commit those changes back to the item and update the main listview? The rest of the code regarding the popup dialog and whatnot is all implemented already.
The Main Dialog:
XAML - Main Dialog
<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="240" Width="350"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView Name="lvDataBinding" Grid.Row="0" Background="LightBlue">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Name: " />
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text=", " />
<TextBlock Text="Age: " />
<TextBlock Text="{Binding Age}" FontWeight="Bold" />
<TextBlock Text=" (" />
<TextBlock Text="{Binding Mail}" TextDecorations="Underline" Foreground="Blue" Cursor="Hand" />
<TextBlock Text=")" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="MouseDoubleClick" Handler="listViewItem_MouseDoubleClick" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Label Content="Selected:" HorizontalAlignment="Left"/>
<Label Content="{Binding SelectedItem.Info, ElementName=lvDataBinding}" HorizontalAlignment="Left" Width="140"/>
<!--<Label Content="{Binding ElementName=lvDataBinding, Path=SelectedItem.Name}" HorizontalAlignment="Left" Margin="72,172,0,0" Width="140"/>-->
</StackPanel>
</Grid>
</Window>
CS - Main Dialog
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<User> items = new List<User>();
items.Add(new User() { Name = "John Doe", Age = 42, Mail = "john#doe-family.com", Info = "A basketball player"});
items.Add(new User() { Name = "Jane Doe", Age = 39, Mail = "jane#doe-family.com", Info = "A soccer player" });
items.Add(new User() { Name = "Sammy Doe", Age = 13, Mail = "sammy.doe#gmail.com", Info = "A hockey player" });
lvDataBinding.ItemsSource = items;
}
private void listViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
ListViewItem item = sender as ListViewItem;
object obj = item.Content;
//User user = sender as User;
Console.WriteLine(item);
// popup window
Window editDialog = new EditWindow();
editDialog.Owner = this;
editDialog.ShowDialog();
if (editDialog.DialogResult.HasValue && editDialog.DialogResult.Value)
{
Console.WriteLine("User pressed OK");
;
}
else
{
Console.WriteLine("User pressed Cancel");
}
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Mail { get; set; }
public string Info { get; set; }
}
}
The Edit Dialog:
XAML - Edit Dialog
<Window x:Class="WpfApplication1.EditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="EditWindow" Height="140" Width="200"
WindowStartupLocation="CenterScreen">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Email: " Grid.Row="0" Grid.Column="0"/>
<TextBox Background="AliceBlue" Grid.Row="0" Grid.Column="1" AcceptsReturn="True" TextWrapping="Wrap" DockPanel.Dock="Right"/>
<Label Content="Info: " Grid.Row="1" Grid.Column="0"/>
<TextBox Background="AliceBlue" Grid.Row="1" Grid.Column="1" AcceptsReturn="True" TextWrapping="Wrap" DockPanel.Dock="Right"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Button Content="Cancel" MinWidth="50" Height="25" Click="ButtonCancelClick"/>
<Button Content="OK" MinWidth="50" Height="25" Click="ButtonOkClick"/>
</StackPanel>
</Grid>
</Window>
CS - Edit Dialog
using System.Windows;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for EditWindow.xaml
/// </summary>
public partial class EditWindow : Window
{
public EditWindow()
{
InitializeComponent();
}
private void ButtonOkClick(object sender, RoutedEventArgs e)
{
DialogResult = true;
this.Close();
}
private void ButtonCancelClick(object sender, RoutedEventArgs e)
{
DialogResult = false;
this.Close();
}
}
}
There are many things to change in order to get this working.
Implement INotifyPropertyChanged on your User class
You cannot have two way binding in wPF without it. Just google and read till you understand how it works.
Create a public CurrentItem property on your EditWindow
(listViewItem_MouseDoubleClick event)
Create a new instance of User class (myUser) and copy all property values from item.Content (you first need to typecast this as a user object)
Set the new CurrentItem of editDialog.CurrentItem = myUser
if the user presses ok copy all properties of editDialog.CurrentItem back to item.Content object (remember this should be already typecasted to User)
Create all Bindins on EditWindow pointing to CurrentItem object.
Hope that helps.