I have two list boxes that both hold collections. The current setup is so that when a item is selected in the left listbox, you can click a button to add that selected state the right listbox. There is an add and remove button for the listboxes that are tied to a custom command with the listbox selected item being the command parameter.
I would like to add a double click functionality to each box so that items can be double clicked to add and remove. I should be able to use my current command execute methods to do this, but have not found a solution to implementing this into a listbox, or listboxitem. I would like to follow MVVM as much as possible, but I've already side stepped that a bit with the current execute methods as i'll show below, but any help would be appreciated. I have not had luck finding anything regarding my specific issue.
<ListBox x:Name="List" ItemContainerStyle="{StaticResource ListBoxItem}" DataContext="{StaticResource VM}"
ItemsSource="{Binding Names, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" DisplayMemberPath="Name"
Style="{StaticResource ResourceKey=ListBox}"/>
<Button Content=">>" Margin="5" Style="{StaticResource ResourceKey=MultiButton}"
CommandParameter="{Binding ElementName=List}"
Command="{Binding Path=AddSelectedItemCommand}"/>
public void AddSelectedItem(object obj)
{
ListBox ListBox = obj as ListBox;
List<Type> List = new List<Type>();
if (Name == null)
Name = new ObservableCollection<Type>();
if (Name != null)
{
foreach (Type item in ListBox.SelectedItems.Cast<object>().ToList())
{
List.Add(item);
Names.Remove(item);
}
foreach (Type listItem in List)
{
var state = Name.FirstOrDefault(aa => aa.Name == listItem.Name);
if (state == null)
{
Name.Add(listItem);
}
}
}
OnPropertyChanged("Name");
OnPropertyChanged("Names");
}
Firstly I would like to let you know that your View Model should know nothing at all about the View itself, so it should know nothing about ListBoxes.
Objects should only know about they things which they depend upon, and not those which depend upon it. Therefore the ViewModel should only know about the collections of data which it is making available to any client.
In your example, what happens when the control is changed from a ListBox -you will have to change your Command.
So, first things first, you will need to change your view model implementation, what you have currently is not MVVM.
Here is an entire listing which should help you along your way:
<Window x:Class="WpfExample.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:WpfExample"
mc:Ignorable="d"
Title="MainWindow" Height="140" Width="410">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Path=Names, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedName}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding Path=DataContext.MyDoubleClickCommand,
RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor} }" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Grid.Column="1" Margin="10,0,0,0" ItemsSource="{Binding Path=NamesTwo, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedNameTwo}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding Path=DataContext.MyOtherDoubleClickCommand,
RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor} }" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
And the code behind
namespace WpfExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
}
Then there is the ViewModel, which you should notice only modifies the collections which are exposed for the View to consume
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Prism.Commands;
namespace WpfExample
{
public class MyViewModel : INotifyPropertyChanged
{
private string _selectedName;
private string _selectedNameTwo;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<string> Names { get; }
= new ObservableCollection<string>(new List<string>
{
"Name1",
"Name2",
"Name3",
"Name4",
"Name5"
});
public ObservableCollection<string> NamesTwo { get; } = new ObservableCollection<string>(new List<string>());
public string SelectedName
{
get { return _selectedName; }
set { _selectedName = value; OnPropertyChanged(); }
}
public string SelectedNameTwo
{
get { return _selectedNameTwo; }
set { _selectedNameTwo = value; OnPropertyChanged(); }
}
public ICommand MyOtherDoubleClickCommand
{
get
{
return new DelegateCommand<string>(name =>
{
NamesTwo.Remove(name);
Names.Add(name);
SelectedNameTwo = "";
});
}
}
public ICommand MyDoubleClickCommand
{
get
{
return new DelegateCommand<string>(name =>
{
Names.Remove(name);
NamesTwo.Add(name);
SelectedName = "";
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
I have used the Prism.Core package for the DelegateCommand object. This is not essential, I just did it for ease
You don't even need the SelectedName and SelectedNameTwo properties if they will not be used whilst processing the ViewModel. I included them for completeness.
.
Edited:
I did not originally notice that this is for a UWP project. I believe the following will work -though it is untested here since I am not set up for UWP on my machine at the moment. I'm not certain of the DoubleClick EventName.
<Page xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core>
<ListBox Grid.Column="1" Margin="10,0,0,0" ItemsSource="{Binding Path=NamesTwo, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedNameTwo}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}" >
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="DoubleClick">
<core:InvokeCommandAction Command="{Binding Path=DataContext.MyDoubleClickCommand,
RelativeSource={RelativeSource AncestorType=Page, Mode=FindAncestor} }"
CommandParameter="{Binding .}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Credit to Bill, as the UWP edit pointed me toward a satisfactory solution.
Firstly, I added a NuGet reference to Microsoft.Xaml.Behaviors.Uwp.Managed
Secondly I added the namespaces Bill mentions to the xaml in which my control is located:
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
Then I added some XAML in my control (List View in this example):
<ListView ...>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="DoubleTapped">
<core:InvokeCommandAction Command="{Binding NavigateUpCommand, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
...
</ListView>
In my case, this was a templated control - and the "DoubleTapped" event name was used successfully :)
The Command was set up in the best way I know; made available as an ICommand get accessor on the in the control class, which used a stock "RelayCommand" implementation
Related
I have a screen displaying a list of items on which the user can click a button to remove the corresponding item from the list.
I am trying to do so using MVVM.
But the item is not aware of the containing list when it gets the action.
I saw some answers here and there, but none of them using out of the box MVVM features I have in my environment
For example that one using PRISM (don't know if I should use that too, is it standard?):
How to properly remove Items from a ListView when the ItemTemplate is a User Control?
Here is the XAML:
<ListView ItemsSource="{Binding MyItemList}" SelectionMode="None" ScrollViewer.VerticalScrollMode="Disabled" ItemContainerTransitions="{x:Null}">
<ListView.ItemTemplate>
<DataTemplate >
<Grid Grid.Row="1" HorizontalAlignment="Stretch" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding ItemClass.Property01, Mode=TwoWay}" />
<Button Grid.Column="1" Command="{Binding RemoveItemCommand}" >
<SymbolIcon Symbol="Cancel" />
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And here is the ModelView list:
private static ObservableCollection<ItemClass> _MyItemList = new ObservableCollection<ItemClass> {
new ItemClass{ Property01 = "Sometext" }
};
public ObservableCollection<ItemClass> MyItemList { get { return _MyItemList; } }
And I want to be able to perform the following (the example of code from the main model view, I could create an item model view if necessary for solving):
public IMvxCommand RemoveItemCommand { get; private set; }
public MyViewModel(IUserDialogs dialogs)
{
RemoveItemCommand = new MvxCommand(RemoveItem);
}
public void RemoveItem(object theItem) { MyItemList.Remove(theItem); }
Add x:Name="listView" attribute to your ListView, then in the template
<Button Grid.Column="1"
Command="{Binding ElementName=listView, Path=DataContext.RemoveItemCommand}"
CommandParameter="{Binding}" >
However, when I face problems like this, I usually just use code behind instead. The reason for that, I can use debugger for C# code in visual studio, but debugging these complex bindings is much harder. Here’s a C# version, the code is IMO cleaner, and easier to debug:
void removeItem_Click( object sender, RoutedEventArgs e )
{
object i = ((FrameworkElement)sender).DataContext;
( this.DataContext as MyViewModel )?.RemoveItem( i );
}
Or maybe that's just my personal preference.
It would be better to have a context menu item on the list view (or a delete button on the page somewhere) to delete the currently selected item(s). You can then get the selection from the list view.
Alternatively you could attach the context menu to the list view item in PrepareContainterForItemOverride (and detach it in the other Override method)
That would be a more standards interaction style.
If you must have the button inside the list view item, then the easiest way to get the list item would probably be to use a visual tree helper to go up from the button to the list view item and then get the actual item from the list view item.
Thanks for all the hints,
Using Soonts answer, I was able to develop a fast solution,
Here is what the final implementation looks like for reference for whoever wants to copy/paste/adapt (note I did not test code as I replaced variables/functions names):
XAML:
<ListView x:Name="ItemClass_ListView" ItemsSource="{Binding MyItemList}" SelectionMode="None" ScrollViewer.VerticalScrollMode="Disabled" ItemContainerTransitions="{x:Null}">
<ListView.ItemTemplate>
<DataTemplate >
<Grid Grid.Row="1" HorizontalAlignment="Stretch" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding ItemClass.Property01, Mode=TwoWay}" />
<Button Grid.Column="1" Command="{Binding ElementName=ItemClass_ListView, Path=DataContext.RemoveItemCommand}" CommandParameter="{Binding}" >
<SymbolIcon Symbol="Cancel" />
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel:
public class MyViewModel : BaseViewModel, INotifyPropertyChanged
{
public IMvxCommand RemoveItemCommand { get; private set; }
public MyViewModel()
{
// Initializing Commands
RemoveItemCommand = new MvxCommand<ItemClass>(OnRemoveItemClick);
}
public void OnRemoveItemClick(ItemClass anItem)
{
// Do stuff...
}
private static ObservableCollection<ItemClass> _MyItemList = new ObservableCollection<ItemClass> {
new ItemClass(),
new ItemClass()
};
public ObservableCollection<ItemClass> MyItemList
{
get { return _MyItemList; }
}
}
I am having an scenario where I show a window to the user and ask them to choose anything by left click on it. See attached pix
So in this Window I have corresponding WindowViewModel following a Prism 6.1.0 framework. I want to bind this click event to the Grid instead of Binding with the each TextBlock each. Is it possible?
if yes, I tried this. In the grid Control my code is this.
<Grid x:Name="Locals1">
<Grid.InputBindings>
<MouseBinding MouseAction="LeftClick"
Gesture="LeftClick" Command="{Binding MouseCommand,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
CommandParameter="{Binding ElementName=Locals1,
Path=SelectedItem, Mode=TwoWay}"
/>
</Grid.InputBindings>
<TextBlock Focusable="True" text="textblock1" />
<TextBlock Focusable="True" text="textblock2" />
<TextBlock Focusable="True" text="textblock3" />
<TextBlock Focusable="True" text="textblock4" />
</Grid>
And in the WindowViewModel I have a code like this.
public WindowViewModel(IEventAggregator eventAggregator)
{
MouseCommand = new DelegateCommand<string>(OnConnection);
}
private void OnConnection(string obj)
{
...
}
But I don't get the TextBlock.Text value in that OnConnection method. Is it really so tough? What I know about WPF and MVVM that we can handle the child click event in the parent control itself. This will reduce duplicate codes.
I know I am doing something definitely wrong. But I don't know what exactly. How can I pass this value from WindowViewModel to the MainWindowViewModel?
I can achieve the same functionality using a binding each in all the textblocks but that will not serve the purpose of Prism. basically all the text Block click events functionality is same only the value of the textblock will be different.
thanks
Honestly, i don't like my answer, but:
You use Grid, not DataGrid and similar, so what is SelectedItem in you context?!
I cant invent how to use pretty binding in this case, so i changed command
public DelegateCommand<Object> MouseCommand { get; set; }
public WindowViewModel(IEventAggregator eventAggregator)
{
MouseCommand = new DelegateCommand<object>(OnConnection);
}
and
private void OnConnection(object obj)
{
var text = GetTextFromClickOnGrid(obj);
}
private string GetTextFromClickOnGrid(object obj)
{
var grid = obj as Grid;
if (grid != null)
{
var mousePos = Mouse.GetPosition(grid);
var itemUnderMouse = VisualTreeHelper.HitTest(grid, mousePos);
var textBlock = itemUnderMouse.VisualHit as TextBlock;
if (textBlock != null)
{
return textBlock.Text;
}
}
var textBlockUnderMouse = Mouse.DirectlyOver as TextBlock;
if (textBlockUnderMouse != null)
{
return textBlockUnderMouse.Text;
}
return string.Empty;
}
and xaml
<Grid Grid.Row="1" x:Name="Locals1">
<Grid.InputBindings>
<MouseBinding MouseAction="LeftClick"
Gesture="LeftClick"
Command="{Binding MouseCommand,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
CommandParameter="{Binding ElementName=Locals1}"
/>
</Grid.InputBindings>
<TextBlock Focusable="True" Text="textblock1" Height="30" Width="100" Margin="115,45,302,84" />
<TextBlock Focusable="True" Text="textblock2" Height="30" Width="100" Margin="115,10,302,119"/>
<TextBlock Focusable="True" Text="textblock3" Height="30" Width="100" Margin="10,45,407,84"/>
<TextBlock Focusable="True" Text="textblock4" Height="30" Width="100" Margin="10,10,407,119"/>
</Grid>
i think you looking this:
<i:Interaction.Triggers>
<i:EventTrigger EventName="LeftClick">
<command:EventToCommand
Command="{Binding Main.MouseCommand ,
Mode=OneWay,
Source={StaticResource Locator}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
binding what you want. Any event to almost any command.
ViewModelLocator:
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
}
/// <summary>
/// Gets the Main property.
/// </summary>
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
}
I have read articles about how commanding works different inside of a listview so I tried that code but when I click nothing happens. I am using Template10. Most of the example I find are for WPF which has incompatible pieces. Just need the bare minimum to get the button click to call the method below. The relevant parts of my code are :
<ListView x:Name="lvMain"
ItemsSource="{Binding LeadSpeakerItems}"
SelectedItem="{Binding Lsi}">
<ListView.ItemTemplate>
...
<Button Content="Details"
Command="{Binding ElementName=Root, Path=RunCommand}"
Grid.Column="1" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
And the code:
public ICommand RunCommand { get; private set; }
public MainPageViewModel()
{
if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
{
LeadSpeakerItems.Add(new LeadSpeakerItem {
VelocifyLeadTitle = "The is the lead title that says somrthing about something and her a number 234-456-3454",
VelocifyFirstName = "BobbiMinajobi",
VelocifyLastName = "Luciferdissikusliskus",
VelocifyLoanAmount = 254000.00,
VelocifyHomeValue = 278000.00
});
}
RunCommand = new DelegateCommand<object>(OnRunCommand, CanRunCommand);
}
private void OnRunCommand(object obj)
{
// use the SelectedCustomer object here...
}
private bool CanRunCommand(object obj)
{
return true;
}
EDIT 1:
How would I get that particular item when the button or the listview item is selected? I am trying to get this piece of code run when that happens. I am missing something.
set
{
Set(ref selectedItem, value);
}
Supposing Root is your page or another control with your viewmodel as DataContext, you should alter your XAML to:
<Button Content="Details"
Command="{Binding ElementName=Root, Path=DataContext.RunCommand}"
Grid.Column="1" />
as RunCommand itself is not known to your Root object, but DataContext (your vm) is.
<Button Content="Details"
Command="{Binding RunCommand}"
Grid.Column="1" />
or
<ListView
x:Name="lvMain"
DataContext={Binding}>
....
</ListView>
<Button
DataContext="{Binding ElementName=lvMain, Path=DataContext}"
Content="Details"
Command="{Binding RunCommand}"
Grid.Column="1" />
try use Template10.Mvvm.DelegateCommand
for example
in viewmodel
public ICommand ItemSelected
{
get
{
return new Template10.Mvvm.DelegateCommand<string>((s) =>
{
NavigationService.Navigate(typeof(DetailPage), s);
});
}
}
add to your page
<page
xmlns:Behaviors="using:Template10.Behaviors"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:vm="using:....ViewModel"
....>
<Page.DataContext>
<vm:ViewModel />
</Page.DataContext>
in your listview
<ListView x:Name="listView" ... ItemsSource="{x:Bind ViewModel.ListItem}" >
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="Tapped">
<Core:InvokeCommandAction Command="{x:Bind ViewModel.ItemSelected}" CommandParameter="{Binding ElementName=listView,Path=SelectedItem}"/>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</ListView>
My project is utilizing MVVM with C#. I've bounded my button command to a RelayCommand, and I wish to get information about my button. I wish to get this information so that I can use it in my RelayCommand. Unfortunately I do not know how to send this information to my RelayCommand, nor do I know which EventArgs I need to receive in my RelayCommand to get this Information.
<ListBox ItemsSource="{Binding Decoration}" x:Name="MyLB">
<ListBox.ItemTemplate>
<DataTemplate>
<Button BorderBrush="Transparent" BorderThickness="0" Command="{Binding DataContext.AddGearCommand, ElementName=MyLB}" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<View:ShielGear/>
</Grid>
<TextBlock Text="HEJ MED DIG LUDER" TextWrapping="Wrap" Grid.Column="1"/>
</Grid>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The ShielGear contains a Path element which the button takes it shape after. The RelayCommand I've bounded the command to is:
AddGearCommand = new RelayCommand<T>(addGear);
private void addGear(T e)
{
}
Furthermore is it possible to parse more than one Type to the relaycommand?
I am also unsure if I should use Commandparameters?
You shouldn't be accessing the button (a UI element) from the ViewModel. This is breaking the separation of concerns and will make life difficult for you later if you need to refactor the UI.
Instead, add a value to the buttons binding which will pass the data you need into the command. Often, this will be the object that is bound to your listboxitem.
<Button Command="{Binding DataContext.AddGearCommand, ElementName=MyLB}" CommandParameter="{Binding}">
Then you need to modify your RelayCommand to be typed with the actual type of your data element.
public RelayCommand<myDataType> AddGearCommand { get;set;}
If you add a name to your ListBox you can use CommandParameter to send the SelectedIndex as a parameter
<ListBox x:Name="myListBox" ...>
In your command
<Button BorderBrush="Transparent" BorderThickness="0" Command="{Binding DataContext.AddGearCommand, ElementName=MyLB}" CommandParameter="{Binding ElementName=myListBox, Path=SelectedIndex}">
Then, your RelayCommand decleration will be as follows:
public RelayCommand<int> AddGearCommand { get; set; }
And in your command :
AddGearCommand = new RelayCommand<int>(selectedIndex =>
{
// do whatever you want
});
Hope this helps
Pass your button name in Commandparameter and in viewmodel cast your parameter as button.
now you can get all info of your button.
XAML:
<Button x:Name="btnPrint" MinWidth="70" Margin="5" Content="Print"
Command="{Binding Print}" CommandParameter="{Binding ElementName=btnPrint}" ></Button>
ViewModel:
private RelayCommand _commandPrint;
public ICommand Print
{
get { return _commandPrint ?? (_commandPrint = new RelayCommand(param => this.PrintGrid(param), Canprint)); }
}
private void PrintGrid(object param)
{
var btn = param as Button;
}
My problem:
I have a listbox with owners of dogs, and i have a listbox with dogs. I want to modify the dogs listbox itemtemplate as the following: DogName(textblock)+DogKind(textblock)+Owners(combobox).The first two was successful, but i cant add the existing owners to the combobox. If i give a name to my combobox like :
<ComboBox x:Name="mycombo" />
i cant see the mycombo variable in the c# code.
The XAML:
<Window x:Class="CodeFirst.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sajat="clr-namespace:CodeFirst"
Title="MainWindow" Height="557.638" Width="721.294"
>
<Grid x:Name="grid1">
<ListBox x:Name="listbox2" HorizontalAlignment="Left" Height="313" Margin="338,10,0,0" VerticalAlignment="Top" Width="250">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text=", "/>
<TextBlock Text="{Binding Path=Kind}"/>
<ComboBox />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
How can i give the itemsource to the combobox, or how can i reach to add the owners?
If you use the DataContext, you can set the Binding like this:
<ComboBox ItemsSource="{Binding Path=DataContext.MyItemsSource, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"></ComboBox>
First of all, in order to work with WPF or other XAML-based technologies, you must understand that
UI is not Data. Data is Data. UI is UI.
This means that you should not manipulate any ComboBox or any other UI elements in code, in order to populate them with data, but instead create a ViewModel and bind these objects to that.
In this example, the Window itself is used as ViewModel because it's a simple example, but you should consider moving all application logic to a separate class:
<Window x:Class="MiscSamples.UIisNotData"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UIisNotData" Height="300" Width="300">
<UniformGrid Rows="1" Columns="2">
<DockPanel>
<TextBlock Text="Owners:" DockPanel.Dock="Top" FontWeight="Bold" TextAlignment="Center" Margin="2"/>
<Button Content="Add" Width="80" DockPanel.Dock="Bottom" Margin="2" Click="AddOwner"/>
<ListBox ItemsSource="{Binding Owners}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name}" x:Name="block"/>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Visibility="Collapsed" x:Name="box"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}}" Value="True">
<Setter TargetName="block" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="box" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
<DockPanel>
<TextBlock Text="Dogs:" DockPanel.Dock="Top" FontWeight="Bold" TextAlignment="Center" Margin="2"/>
<ListBox ItemsSource="{Binding Dogs}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<ComboBox ItemsSource="{Binding DataContext.Owners, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
SelectedItem="{Binding Owner}" DisplayMemberPath="Name"
DockPanel.Dock="Right" Width="100"/>
<TextBlock>
<Run Text="{Binding Name}"/>
<Run Text=", "/>
<Run Text="{Binding Kind}"/>
</TextBlock>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</UniformGrid>
</Window>
Code Behind (This code should be placed in a ViewModel):
public partial class UIisNotData : Window
{
public ObservableCollection<Owner> Owners { get; set; }
public ObservableCollection<string> Kinds { get; set; }
public ObservableCollection<Dog> Dogs { get; set; }
public UIisNotData()
{
InitializeComponent();
Owners = new ObservableCollection<Owner>
{
new Owner() {Name = "Jack"},
new Owner() {Name = "Mike"},
new Owner() {Name = "Kirk"},
new Owner() {Name = "John"},
};
Kinds = new ObservableCollection<string>
{
"Affenpinscher",
"Afghan Hound",
"Airedale Terrier",
"Akita"
//.. All the rest of dog Breeds taken from http://www.petmd.com/dog/breeds?breed_list=az#.UVsQKpPcmQo
};
Dogs = new ObservableCollection<Dog>
{
new Dog() {Name = "Bobby", Kind = Kinds[0], Owner = Owners[0]},
new Dog() {Name = "Fido", Kind = Kinds[1], Owner = Owners[1]},
new Dog() {Name = "Toby", Kind = Kinds[2], Owner = Owners[2]}
};
DataContext = this;
}
private void AddOwner(object sender, RoutedEventArgs e)
{
Owners.Add(new Owner(){Name = "New Owner"});
}
}
Data Model:
public class Owner : PropertyChangedBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public class Dog: PropertyChangedBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
private Owner _owner;
public Owner Owner
{
get { return _owner; }
set
{
_owner = value;
OnPropertyChanged("Owner");
}
}
private string _kind;
public string Kind
{
get { return _kind; }
set
{
_kind = value;
OnPropertyChanged("Kind");
}
}
}
PropertyChangedBase Class:
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Result:
There are 3 important aspects you need to consider about this example:
I am in no way manipulating UI elements in code. That's completely unnecessary most of the time in WPF.
The classes from the Data Model implement INotifyPropertyChanged in order to support 2-way binding in WPF.
The Collections are of type ObservableCollection<T> in order to support automatic notification when elements are added/removed from the collection (in order to automatically update the ListBoxes, etc).
Another thing you may notice is that the XAML elements in my example have no specific size or Margin values. Things like Margin="338,10,0,0" is usually what you get from the Visual Studio designer and indicates a poorly structured layout. I recommend you look at the Layout elements in WPF (DockPanel, StackPanel, Grid, UniformGrid, WrapPanel, etc), and start coding the XAML yourself instead of using the designer. This will allow a much higher level of scalability and will also save you from the nuances of Fixed-position elements.