I am currently experimenting with WPF.
One thing, I wanted to do was a master to detail selection over multiple comboboxes.
I have a ViewModel with GroupItems that i use as ItemSource for the first combobox. These GroupItems have a Property called Childs, which includes a List of items that belong to this group.
I can't find a way to bind the comboBox1.SelectedItem.Childs as Itemsource for the second comboBox.
Right now I only got to
ItemsSource="{Binding ElementName=comboBox1, Path=SelectedItem}"
But I don't get the Property of the SelectedItem. How can this be done? Or is this not the WPF way to this?
Is there any good website to learn how to select different elements? Eplaining Path, XPath, Source and everything?
Thanks for any help.
Your binding above isn't attempting to bind to Childs, only SelectedItem.
Try something like this:
Window1.xaml
<Window x:Class="WpfApplication5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ComboBox x:Name="_groups" ItemsSource="{Binding Groups}" DisplayMemberPath="Name"/>
<ComboBox ItemsSource="{Binding SelectedItem.Items, ElementName=_groups}"/>
</StackPanel>
</Window>
Window1.xaml.cs
using System.Windows;
namespace WpfApplication5 {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
var model = new ViewModel();
var g1 = new Group { Name = "Group1" };
g1._items.Add("G1C1");
g1._items.Add("G1C2");
g1._items.Add("G1C3");
model._groups.Add(g1);
var g2 = new Group { Name = "Group2" };
g2._items.Add("G2C1");
g2._items.Add("G2C2");
g2._items.Add("G2C3");
model._groups.Add(g2);
var g3 = new Group { Name = "Group3" };
g3._items.Add("G3C1");
g3._items.Add("G3C2");
g3._items.Add("G3C3");
model._groups.Add(g3);
DataContext = model;
}
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
namespace WpfApplication5
{
public class Group {
internal List<String> _items = new List<string>();
public IEnumerable<String> Items {
get { return _items; }
}
public String Name { get; set; }
}
public class ViewModel
{
internal List<Group> _groups = new List<Group>();
public IEnumerable<Group> Groups
{
get { return _groups; }
}
}
}
Related
I am a beginner in C# and WPF .
I have created a user control LogTable.atxml which contains a DataGrid and added it to the MainWindow.xaml .
The Table is displayed but the contents are not being fetched.
I think the issue is im not able to sent the Itemsource in the right way.
[Result]Please help.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TableTest.UserControls
{
class Tabledata
{
string A{ get; set; }
string B { get; set; }
string C { get; set; }
public Tabledata(string a, string b, string c)
{
A = a;
B = b;
C =c;
}
}
}
namespace TableTest.UserControls
{
/// <summary>
/// Interaction logic for LogTable.xaml
/// </summary>
public partial class LogTable : UserControl
{
ObservableCollection<Tabledata> list;
public LogTable()
{
InitializeComponent();
list = getTableDetails();
this.logGrid.ItemsSource = list;
}
private ObservableCollection<Tabledata> getTableDetails()
{
ObservableCollection<Tabledata> list= new ObservableCollection<Tabledata>();
Tabledata data = new Tabledata("aaa", "aaa", "aaa");
Tabledata data1 = new Tabledata("bbb", "aaa", "aaa");
Tabledata data2 = new Tabledata("ccc", "aaa", "aaa");
list.Add(data);
list.Add(data1);
list.Add(data2);
return list;
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:UserControls="clr-namespace:TableTest.UserControls" x:Class="TableTest.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<UserControls:LogTable x:Name="logtable" HorizontalAlignment="Left" Margin="0,209,0,0" VerticalAlignment="Top" Width="287" Height="111"/>
</Grid>
</Window>
<UserControl x:Class="TableTest.UserControls.LogTable"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
>
<DataGrid x:Name="logGrid" AutoGenerateColumns="False"
Height="290"
HorizontalAlignment="Left"
VerticalAlignment="Top" Width="290"
ItemsSource="{Binding list}"
>
<DataGrid.Columns >
<DataGridTextColumn Binding="{Binding Path=A}" MinWidth="50" Header="Column 1"/>
<DataGridTextColumn Binding="{Binding Path=B}" MinWidth="50" Header="Column 2"/>
<DataGridTextColumn Binding="{Binding Path=C}" MinWidth="50" Header="Column 3"/>
</DataGrid.Columns>
</DataGrid>
</UserControl>
I think you need to do a few things
1. Your ItemSource needs to bind to a property. So your code should look something like
public partial class LogTable : UserControl
{
public ObservableCollection<Tabledata> list {get;set;}
public LogTable()
{
InitializeComponent();
DataContext=this;
list = new ObservableCollection<TableData>();
list = getTableDetails();
this.logGrid.ItemsSource = list;
}
You need to set your data context of your user control. If you are just using the codebehind you can get away with setting the DataContext in your usercontrols constructor like in the code above. But probably in the future you are going to want to use the mvvm pattern and set your datacontext to your viewmodel.
Note: You will need to set your datacontext of the mainwindow if you want to access any information from that window's codebehind (or whatever you want to bind data from).
Here is a good resource to read up on mvvm.
Update: Just saw your xaml. Since you named the Datagrid you can actually get away with not setting the DataContext as your as setting the ItemSource directly in your code. However, since you don't have your datacontext set you can remove the ItemSource={Binding list} from your xaml. That will only work if you have the list property available on your DataContext.
Update 2: You also need to make your properties public on your TableData class. then it will work
class Tabledata
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
public Tabledata(string a, string b, string c)
{
A = a;
B = b;
C = c;
}
}
I have a ComboBox bound to a collection of animals. From it I select my favourite animal. I need a static null item above the bound items. I declare it using a CompositeCollection. When the ComboBox is bound it does not select my initial favourite animal. How can I fix that? Similar problem here but still unresolved.
Observations:
Binding to the static item works i.e. if I don't have an initial favourite animal the static item gets selected.
The problem disappears if the static item is removed. Of course this would make the CompositeCollection and this whole question obsolete.
I already applied these measures:
A CollectionContainer cannot bind directly to a property as outlined here.
The composite collection is also moved to a static resource as suggested here.
Complete C# code and XAML to demonstrate the problem:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public class Animal
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Zoo
{
private IEnumerable<Animal> _animals = new Animal[]
{
new Animal() { Id = 1, Name = "Tom" },
new Animal() { Id = 2, Name = "Jerry" }
};
public Zoo(int initialId)
{
FavouriteId = initialId;
}
public int FavouriteId { get; set; }
public IEnumerable<Animal> Animals { get { return _animals; } }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void BindComboBox(object sender, RoutedEventArgs e)
{
// Selecting the static item by default works.
//DataContext = new Zoo(-1);
// Selecting "Jerry" by default does not work.
DataContext = new Zoo(2);
}
}
}
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:local="clr-namespace:WpfApplication1">
<Window.Resources>
<CollectionViewSource x:Key="AnimalsBridge" Source="{Binding Path=Animals}" />
<CompositeCollection x:Key="AnimalsWithNullItem">
<local:Animal Id="-1" Name="Pick someone..."/>
<CollectionContainer Collection="{Binding Source={StaticResource AnimalsBridge}}" />
</CompositeCollection>
</Window.Resources>
<StackPanel>
<Button Content="Bind" Click="BindComboBox"/>
<ComboBox x:Name="cmbFavourite"
SelectedValue="{Binding Path=FavouriteId}"
SelectedValuePath="Id" DisplayMemberPath="Name"
ItemsSource="{StaticResource AnimalsWithNullItem}"/>
</StackPanel>
</Window>
I dont have a solution to your problem but rather an alternative. I personally have view models dedicated to each view. I would then have a property on the view model to add the null value as required. I prever this method since it allows for better unit testing of my viewmodel.
For your example add:
public class ZooViewModel
{
.....
public IEnumerable<Animal> Animals { get { return _animals; } }
public IEnumerable<Animal> AnimalsWithNull { get { return _animals.WithDefault(new Animal() { Id = -1, Name = "Please select one" }); } }
}
The magic component
public static class EnumerableExtend {
public static IEnumerable<T> WithDefault<T>(this IEnumerable<T> enumerable,T defaultValue) {
yield return defaultValue;
foreach (var value in enumerable) {
yield return value;
}
}
}
Then in your XAML you just bind to
ComboBox x:Name="cmbFavourite"
SelectedValue="{Binding Path=FavouriteId}"
SelectedValuePath="Id" DisplayMemberPath="Name"
ItemsSource="{Binding AnimalsWithNull }"/>
Now you are binding directly to the source and can control the binding as normal. Also note because we are using "yield" we are not creating a new enum but rather just iterating over the existing list.
I am trying the absolute simplest use of HierarchicalDataTemplate to bind nested data to a WPF TreeView. For some reason, the children of my tree are not visible:
Here is the entire 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:src="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<TreeView Name="ctTree">
<TreeView.Resources>
<HierarchicalDataTemplate DataType = "{x:Type src:MyClass}"
ItemsSource = "{Binding Path=Children}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Window>
and here’s all of C# behind this, apart from usings and the namespace:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var collection = new ObservableCollection<MyClass>
{
new MyClass { Name = "parent one" },
new MyClass { Name = "parent two" },
};
collection[0].Children.Add(new MyClass { Name = "child one" });
collection[0].Children.Add(new MyClass { Name = "child two" });
ctTree.ItemsSource = collection;
}
}
class MyClass
{
public string Name { get; set; }
public ObservableCollection<MyClass> Children
= new ObservableCollection<MyClass>();
}
Note that the data template does actually apply to the items: the data is taken from the Name property, and if the template didn’t apply would show as "MyClass" instead.
How do I get the children to show? I seem to be doing exactly the same thing as all examples on HierarchicalDataTemplate.
MyClass.Children is a field, not a property. You cannot bind to a field, convert Children field to a property and everything should work then:
class MyClass
{
public string Name { get; set; }
public ObservableCollection<MyClass> Children { get; private set; }
public MyClass()
{
Children = new ObservableCollection<MyClass>();
}
}
I think Treeview.Resources is the wrong place for that. You want to put your template in Treeview.ItemTemplate.
I'm surprised that no one has asked this before here... well, at least I haven't found an answer here or anywhere else, actually.
I have a ComboBox that is databound to an ObservableCollection. Everything worked great until the guys wanted the contents sorted. No problem -- I end up changing the simple property out:
public ObservableCollection<string> CandyNames { get; set; } // instantiated in constructor
for something like this:
private ObservableCollection<string> _candy_names; // instantiated in constructor
public ObservableCollection<string> CandyNames
{
get {
_candy_names = new ObservableCollection<string>(_candy_names.OrderBy( i => i));
return _candy_names;
}
set {
_candy_names = value;
}
}
This post is really two questions in one:
How can I sort a simple ComboBox of strings in XAML only. I have researched this and can only find info about a SortDescription class, and this is the closest implementation I could find, but it wasn't for a ComboBox.
Once I implemented the sorting in code-behind, it my databinding was broken; when I added new items to the ObservableCollection, the ComboBox items didn't update! I don't see how that happened, because I didn't assign a name to my ComboBox and manipulate it directly, which is what typically breaks the binding.
Thanks for your help!
You can use a CollectionViewSource to do the sorting in XAML, however you need to refresh it's view if the underlying collection changes.
XAML:
<Window x:Class="CBSortTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
Height="300" Width="300">
<Window.Resources>
<CollectionViewSource Source="{Binding Path=CandyNames}" x:Key="cvs">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<StackPanel>
<ComboBox ItemsSource="{Binding Source={StaticResource cvs}}" />
<Button Content="Add" Click="OnAdd" />
</StackPanel>
</Window>
Code behind:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Data;
namespace CBSortTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
CandyNames = new ObservableCollection<string>();
OnAdd(this, null);
OnAdd(this, null);
OnAdd(this, null);
OnAdd(this, null);
DataContext = this;
CandyNames.CollectionChanged +=
(sender, e) =>
{
CollectionViewSource viewSource =
FindResource("cvs") as CollectionViewSource;
viewSource.View.Refresh();
};
}
public ObservableCollection<string> CandyNames { get; set; }
private void OnAdd(object sender, RoutedEventArgs e)
{
CandyNames.Add("Candy " + _random.Next(100));
}
private Random _random = new Random();
}
}
Is there any way, how to force ObservableCollection to fire CollectionChanged?
I have a ObservableCollection of objects ListBox item source, so every time I add/remove item to collection, ListBox changes accordingly, but when I change properties of some objects in collection, ListBox still renders the old values.
Even if I do modify some properties and then add/remove object to the collection, nothing happens, I still see old values.
Is there any other way around to do this? I found interface INotifyPropertyChanged, but I don't know how to use it.
I agree with Matt's comments above. Here's a small piece of code to show how to implement the INotifyPropertyChanged.
===========
Code-behind
===========
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Documents;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
Nicknames names;
public Window1()
{
InitializeComponent();
this.addButton.Click += addButton_Click;
this.names = new Nicknames();
dockPanel.DataContext = this.names;
}
void addButton_Click(object sender, RoutedEventArgs e)
{
this.names.Add(new Nickname(myName.Text, myNick.Text));
}
}
public class Nicknames : System.Collections.ObjectModel.ObservableCollection<Nickname> { }
public class Nickname : System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
void Notify(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propName));
}
}
string name;
public string Name
{
get { return name; }
set
{
name = value;
Notify("Name");
}
}
string nick;
public string Nick
{
get { return nick; }
set
{
nick = value;
Notify("Nick");
}
}
public Nickname() : this("name", "nick") { }
public Nickname(string name, string nick)
{
this.name = name;
this.nick = nick;
}
public override string ToString()
{
return Name.ToString() + " " + Nick.ToString();
}
}
}
XAML
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<DockPanel x:Name="dockPanel">
<TextBlock DockPanel.Dock="Top">
<TextBlock VerticalAlignment="Center">Name: </TextBlock>
<TextBox Text="{Binding Path=Name}" Name="myName" />
<TextBlock VerticalAlignment="Center">Nick: </TextBlock>
<TextBox Text="{Binding Path=Nick}" Name="myNick" />
</TextBlock>
<Button DockPanel.Dock="Bottom" x:Name="addButton">Add</Button>
<ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" />
</DockPanel>
</Grid>
Modifying properties on the items in your collection won't fire NotifyCollectionChanged on the collection itself - it hasn't changed the collection, after all.
You're on the right track with INotifyPropertyChanged. You'll need to implement that interface on the class that your list contains. So if your collection is ObservableCollection<Foo>, make sure your Foo class implements INotifyPropertyChanged.