Quick question..
I have a List of objects of this class:
public class Whatever
{
public string Name { get; set; }
public List<blaBla> m_blaBla { get; set; }
// ..
}
And I want to link the List<Whatever> to a ComboxBox, where the user sees the Name of each Whatever object. How can I do that?
You could either use ComboBox.ItemTemplate like this:
C#:
List<Whatever> lst = new List<Whatever>();
public MainWindow()
{
InitializeComponent();
cmb.ItemsSource = lst;
}
XAML:
<ComboBox Name="cmb">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Or use DisplayMemberPath:
<ComboBox Name="cmb" DisplayMemberPath="Name">
</ComboBox>
Or just override the ToString() function and it will do the job for you:
public class Whatever
{
public string Name { get; set; }
public List<blaBla> m_blaBla { get; set; }
// ..
public override string ToString()
{
return Name;
}
}
And then:
List<Whatever> MyList = new List<Whatever>();
public MainWindow()
{
InitializeComponent();
MyComboBox.ItemsSource = MyList;
}
Create a Viewmodel:
public ObservableCollection<Whatever> WhCol
{
get { return this.Name; }
set { }
}
And then a Matching View
<ComboBox DisplayMemberPath="Name" ItemsSource="{Binding WhCol}" />
According to Model-View-Modelview Pattern
This is more suited if you wan't to make changes based on user input. (Which is kinda rare for a combobox).
Related
I am trying to bind my ViewModel to my ComboBox. I have ViewModel class defined like this:
class ViewModel
{
public ViewModel()
{
this.Car= "VW";
}
public string Car{ get; set; }
}
I set this ViewModel as DataContext in Window_Load like:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new CarModel();
}
Then in my xaml, I do this to bind my ComboBox to this ViewModel. I want to show the "VW" as selected by default in my ComboBox:
<ComboBox Name="cbCar" SelectedItem="{Binding Car, UpdateSourceTrigger=PropertyChanged}">
<ComboBoxItem Tag="Mazda">Mazda</ComboBoxItem>
<ComboBoxItem Tag="VW">VW</ComboBoxItem>
<ComboBoxItem Tag="Audi">Audi</ComboBoxItem>
</ComboBox>
I have 2 questions:
How do I set default value selected in Combo Box to "VW" (once form loads, it should show "VW" in combo box).
Instead of setting ComboBoxItems like above in xaml, how to I set it in my ViewModel and then load these in ComboBox?
Thanks,
UPDATE:
So far, I manage to implement this but I get error as below in the ViewModel c-tor:
namespace MyData
{
class ViewModel
{
public ViewModel()
{
this.Make = "";
this.Year = 1;
this.DefaultCar = "VW"; //this is where I get error 'No string allowed'
}
public IEnumerable<Car> Cars
{
get
{
var cars = new Car[] { new Car{Model="Mazda"}, new Car{Model="VW"}, new Car{Model="Audi"} };
DefaultCar = cars.FirstOrDefault(car => car.Model == "VW");
}
}
public string Make { get; set; }
public int Year { get; set; }
public Car DefaultCar { get; set; }
}
class Car
{
public string Model { get; set; }
}
}
As you are going to implement MVVM it will be a lot better if you start to think in objects to represent Cars in your application:
public class ViewModel
{
public Car SelectedCar{ get; set; }
public ObservableCollection<Car> Cars{
get {
var cars = new ObservableCollection(YOUR_DATA_STORE.Cars.ToList());
SelectedCar = cars.FirstOrDefault(car=>car.Model == "VW");
return cars;
}
}
}
public class Car
{
public string Model {get;set;}
public string Make { get; set; }
public int Year { get; set; }
}
Your Xaml:
<ComboBox SelectedItem="{Binding SelectedCar}", ItemsSource="{Binding Cars}"
UpdateSourceTrigger=PropertyChanged}"/>
Default Value:
If you set viewModel.Car = "VW", then it should auto-select that item in the combo box.
For this to work you will need to either implement INotifyPropertyChanged or set Car before you set DataContext.
INotifyPropertyChanged implementation might look like:
class ViewModel : INotifyPropertyChanged
{
private string car;
public ViewModel()
{
this.Car = "VW";
this.Cars = new ObservableCollection<string>() { "Mazda", "VW", "Audi" };
}
public string Car
{
get
{
return this.car;
}
set
{
if (this.car != value)
{
this.car = value;
OnPropertyChanged();
}
}
}
public ObservableCollection<string> Cars { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
2.
Bind ItemsSource and SelectedItem.
<ComboBox ItemsSource="{Binding Cars}"
SelectedItem="{Binding Car, Mode=TwoWay}">
</ComboBox>
You can also set ComboBox.ItemTemplate if your source or view is more complex than just displaying a string:
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
In the view model just add a list property:
public ObservableCollection<string> Cars { get; set; }
It doesn't have to be ObservableCollection but that type will auto-update the UI whenever you change the collection.
Maybe this is quiet simple question but I have a problems with construction of a template for my treeview. I have some classes:
public class A //main class
{
public B sth { get; set; }
public C sthelse { get; set; }
public A()
{
this.sth = new B(1000, "sth");
this.sthelse = new C();
}
}
public class B
{
public D sth { get; set; }
public B(ulong data, String abc)
{
this.sth = new D(data, abc);
}
}
public class D
{
public ulong data { get; private set; }
public String abc { get; private set; }
public D(ulong data, String abc)
{
this.data = data;
this.abc = abc;
}
}
And my question is how can I put it into treeview. I was testing HierarchicalDataTemplate but problem is that it have to be bound to collection. Any ideas how to create treeview like this:
A
B
D
data
abc
C
Is it possible?
I am using this code:
<TreeView ItemsSource="{Binding}" ItemTemplate="{StaticResource phy}" />
<Window.Resources>
<DataTemplate x:Key="d">
<StackPanel Orientation="Vertical">
<!-- Maybe there should be pairs property - value, maybe grid or whatever -->
<TextBlock Text="{Binding Path=data}" />
<TextBlock Text="{Binding Path=abc}" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="b" ItemsSource="{Binding Path=sth}" ItemTemplate="{StaticResource ResourceKey=d}">
<TextBlock Text="D" />
</HierarchicalDataTemplate>
<!-- Cant bind also attribute C -->
<HierarchicalDataTemplate x:Key="phy" ItemsSource="{Binding Path=sth}" ItemTemplate="{StaticResource ResourceKey=b}">
<TextBlock Text="PHY" />
</HierarchicalDataTemplate>
</Window.Resources>
In code is:
public ObservableCollection<A> data { get; private set; }
And in constructor:
data = new ObservableCollection<A>();
treeView1.DataContext = data;
data.Add(new A());
ItemsSource property values must be IEnumerable. There's no way to avoid this. You can expose IEnumerables in a very simple way such as below, but I would recommend a better object model than this. You can take these classes and bind the ItemsSource properties of the tree and the HierarchicalDataTemplate to this new Nodes property.
public class A //main class
{
public B sth { get; set; }
public C sthelse { get; set; }
public A()
{
this.sth = new B(1000, "sth");
this.sthelse = new C();
}
public IEnumerable<object> Nodes
{
get
{
yield return B;
yield return C;
}
}
}
public class B
{
public D sth { get; set; }
public B(ulong data, String abc)
{
this.sth = new D(data, abc);
}
public IEnumerable<object> Nodes
{
get
{
yield return D;
}
}
}
public class D
{
public ulong data { get; private set; }
public String abc { get; private set; }
public D(ulong data, String abc)
{
this.data = data;
this.abc = abc;
}
public IEnumerable<object> Nodes
{
get
{
yield return data;
yield return abc;
}
}
}
I'm trying to create a treeview with an ObservableCollection, but I'm having a problem on obtaining the elements from a list in my object. What happens is that I can retrieve the group name, it displays on the list just fine, but then instead of getting the usernames I get a blank space. Here's some code:
<TreeView x:Name="treeUsers">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=GroupName}" >
<Grid Margin="1">
<CheckBox Content="{Binding Path=UserList.Name}" />
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
In this code, I obtain the users from a list, then separate them into different groups, and return the list to my Viewer.
private Dictionary<string, GroupDTO> _groupsDTO= new Dictionary<string, GroupDTO>();
_groupsDTO.Add("GENERAL", new GroupDTO("GENERAL"));
foreach (NetSendUser user in ObtainUserList())
{
UserDTO userDTO = new UserDTO (user);
_groupsDTO["GENERAL"].UserList.Add(userDTO );
if (!_groupsDTO.ContainsKey(user.Area))
_groupsDTO.Add(user.Area, new GroupDTO(user.Area));
_groupsDTO[user.Area].UserList.Add(userDTO);
}
ObservableCollection<GroupDTO> groups= new ObservableCollection<GroupDTO>(_groupDTO.Values.ToArray());
treeUsers.ItemsSource = groups;
And here's my GroupDTO:
public class GroupDTO: ObjectDTO
{
internal GroupDTO(string name)
: base()
{
_groupName = name;
}
private string _groupName = default(string);
public string GroupName
{
get { return _groupName ; }
set
{
_groupName = value;
OnPropertyChanged("GroupName");
}
}
private ObservableCollection<UserDTO> _userList= new ObservableCollection<UserDTO>();
public ObservableCollection<FuncionarioDTO> UserList
{
get { return _userList; }
}
}
I'm not even sure how your application is working at all. You are binding GroupName (property of type string) to an ItemsSource (which requires collection). And then you are trying to bind to UserList.Name, but it is a collection of items, it doesn't expose Name property, User does. Here is a simplified working example.
Xaml:
<Window x:Class="WpfTestBench.TreeSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfTestBench="clr-namespace:WpfTestBench"
Title="Tree sample" Height="300" Width="300">
<TreeView ItemsSource="{Binding Groups}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="wpfTestBench:Group" ItemsSource="{Binding Users}">
<Label Content="{Binding Name}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="wpfTestBench:User">
<CheckBox Content="{Binding Path=Username}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Codebehind:
using System.Collections.Generic;
namespace WpfTestBench
{
public partial class TreeSample
{
public TreeSample()
{
InitializeComponent();
DataContext = new Context();
}
}
public class Context
{
public Context()
{
Groups = new List<Group>();
var mainGroup = new Group
{
Name = "Main",
Users = new List<User> { new User("John"), new User("Bill") }
};
var secondaryGroup = new Group
{
Name = "Secondary",
Users = new List<User> { new User("Tom"), new User("Phil") }
};
Groups.Add(mainGroup);
Groups.Add(secondaryGroup);
}
public IList<Group> Groups { get; private set; }
}
public class Group
{
public string Name { get; set; }
public IList<User> Users { get; set; }
}
public class User
{
public User(string name)
{
Username = name;
}
public string Username { get; private set; }
}
}
Execution result:
I am currently implementing the application that displays hierarchy using ListBoxes (please do not suggest using TreeView, ListBoxes are needed).
It looks like that in the article: WPF’s CollectionViewSource (with source code).
Classes:
public class Mountains : ObservableCollection<Mountain>
{
public ObservableCollection<Lift> Lifts { get; }
public string Name { get; }
}
public class Lift
{
public ObservableCollection<string> Runs { get; }
}
The example uses CollectionViewSource instances (see XAML) to simplify the design.
An instance of Mountains class is the DataContext for the window.
The problem is: I would like that the Mountains class to have SelectedRun property and it should be set to currently selected run.
public class Mountains : ObservableCollection<Mountain>
{
public ObservableCollection<Lift> Lifts { get; }
public string Name { get; }
public string SelectedRun { get; set; }
}
Maybe I've missed something basic principle, but how can I achieve this?
You may want to read about the use of '/' in bindings. See the section 'current item pointers' on this MSDN article.
Here's my solution:
Xaml
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Margin="5" Grid.Row="0" Grid.Column="0" Text="Mountains"/>
<TextBlock Margin="5" Grid.Row="0" Grid.Column="1" Text="Lifts"/>
<TextBlock Margin="5" Grid.Row="0" Grid.Column="2" Text="Runs"/>
<ListBox Grid.Row="1" Grid.Column="0" Margin="5"
ItemsSource="{Binding Mountains}" DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True" />
<ListBox Grid.Row="1" Grid.Column="1" Margin="5"
ItemsSource="{Binding Mountains/Lifts}" DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True"/>
<ListBox Grid.Row="1" Grid.Column="2" Margin="5"
ItemsSource="{Binding Mountains/Lifts/Runs}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedRun}"/>
</Grid>
C# (note, you don't need to implement INotifyPropertyChanged unless the properties will be changed and not just selected)
public class MountainsViewModel
{
public MountainsViewModel()
{
Mountains = new ObservableCollection<Mountain>
{
new Mountain
{
Name = "Whistler",
Lifts = new ObservableCollection<Lift>
{
new Lift
{
Name = "Big Red",
Runs = new ObservableCollection<string>
{
"Headwall",
"Fisheye",
"Jimmy's"
}
},
new Lift
{
Name = "Garbanzo",
Runs = new ObservableCollection<string>
{
"Headwall1",
"Fisheye1",
"Jimmy's1"
}
},
new Lift {Name = "Orange"},
}
},
new Mountain
{
Name = "Stevens",
Lifts = new ObservableCollection<Lift>
{
new Lift {Name = "One"},
new Lift {Name = "Two"},
new Lift {Name = "Three"},
}
},
new Mountain {Name = "Crystal"},
};
}
public string Name { get; set; }
private string _selectedRun;
public string SelectedRun
{
get { return _selectedRun; }
set
{
Debug.WriteLine(value);
_selectedRun = value;
}
}
public ObservableCollection<Mountain> Mountains { get; set; }
}
public class Mountain
{
public string Name { get; set; }
public ObservableCollection<Lift> Lifts { get; set; }
}
public class Lift
{
public string Name { get; set; }
public ObservableCollection<string> Runs { get; set; }
}
Here's how I would do it. You want to make sure that you fire the INotifyPropertyChanged event when setting the properties. To get the Selected Run you'll have to get MainViewModel.SelectedMountain.SelectedLift.SelectedRun.
public class MainViewModel: ViewModelBae
{
ObservableCollection<MountainViewModel> mountains
public ObservableCollection<MountainViewModel> Mountains
{
get { return mountains; }
set
{
if (mountains != value)
{
mountains = value;
RaisePropertyChanged("Mountains");
}
}
}
MountainViewModel selectedMountain
public MountainViewModel SelectedMountain
{
get { return selectedMountain; }
set
{
if (selectedMountain != value)
{
selectedMountain = value;
RaisePropertyChanged("SelectedMountain");
}
}
}
}
public class MountainViewModel: ViewModelBae
{
ObservableCollection<LiftViewModel> lifts
public ObservableCollection<LiftViewModel> Lifts
{
get { return lifts; }
set
{
if (lifts != value)
{
lifts = value;
RaisePropertyChanged("Lifts");
}
}
}
LiftViewModel selectedLift
public LiftViewModel SelectedLift
{
get { return selectedLift; }
set
{
if (selectedLift != value)
{
selectedLift = value;
RaisePropertyChanged("SelectedLift");
}
}
}
}
public class LiftViewModel: ViewModelBae
{
ObservableCollection<string> runs
public ObservableCollection<string> Runs
{
get { return runs; }
set
{
if (runs != value)
{
runs = value;
RaisePropertyChanged("Runs");
}
}
}
string selectedRun
public string SelectedRun
{
get { return selectedLift; }
set
{
if (selectedLift != value)
{
selectedLift = value;
RaisePropertyChanged("SelectedLift");
}
}
}
}
<ListBox ItemsSource="{Binding Mountains}" SelectedItem="{Binding SelectedMountain, Mode=TwoWay}">
<ListBox ItemsSource="{Binding SelectedMountain.Lifts}" SelectedItem="{Binding SelectedMountain.SelectedLift, Mode=TwoWay}">
<ListBox ItemsSource="{Binding SelectedMountain.SelectedLift.Runs}" SelectedItem="{Binding SelectedMountain.SelectedLift.SelectedRun, Mode=TwoWay}">
Your ViewModel should not also be a collection, it should contain collections and properties which are bound to the view. SelectedRun should be a property of this ViewModel (MountainViewModel) not Mountains. MountainViewModel should expose the Mountains collection and SelectedRun and should be bound to the listboxes' ItemsSource and SelectedItem.
I have a WPF UserControl with a ListBox and ContentPanel. The ListBox is bound to a ObservableCollection that has apples and oranges in it.
What is considered the proper way to have it setup so if I select an apple I see an AppleEditor on the right and if I select an orange an OrangeEditor shows up in the content panel?
I would suggest using DataTemplating to create and apply the different editors. Depending on how different your 'apples' and 'oranges' are I would recommend using a DataTemplateSelector. Also, if they had something like a Type property you could also use DataTriggers to switch out the editors.
Lets set up a small sample with apples and oranges. They'll have some shared properties, and a few different properties as well. And then we can create an ObservableCollection of the base IFruits to use in the UI.
public partial class Window1 : Window
{
public ObservableCollection<IFruit> Fruits { get; set; }
public Window1()
{
InitializeComponent();
Fruits = new ObservableCollection<IFruit>();
Fruits.Add(new Apple { AppleType = "Granny Smith", HasWorms = false });
Fruits.Add(new Orange { OrangeType = "Florida Orange", VitaminCContent = 75 });
Fruits.Add(new Apple { AppleType = "Red Delicious", HasWorms = true });
Fruits.Add(new Orange { OrangeType = "Navel Orange", VitaminCContent = 130 });
this.DataContext = this;
}
}
public interface IFruit
{
string Name { get; }
string Color { get; }
}
public class Apple : IFruit
{
public Apple() { }
public string AppleType { get; set; }
public bool HasWorms { get; set; }
#region IFruit Members
public string Name { get { return "Apple"; } }
public string Color { get { return "Red"; } }
#endregion
}
public class Orange : IFruit
{
public Orange() { }
public string OrangeType { get; set; }
public int VitaminCContent { get; set; }
#region IFruit Members
public string Name { get { return "Orange"; } }
public string Color { get { return "Orange"; } }
#endregion
}
Next, we can create DataTemplateSelector, that will just check the type of the Fruit and assign the correct DataTemplate.
public class FruitTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
string templateKey = null;
if (item is Orange)
{
templateKey = "OrangeTemplate";
}
else if (item is Apple)
{
templateKey = "AppleTemplate";
}
if (templateKey != null)
{
return (DataTemplate)((FrameworkElement)container).FindResource(templateKey);
}
else
{
return base.SelectTemplate(item, container);
}
}
}
Then in the UI, we can create the two templates for Apples and Oranges, and use the selector to determine which gets applied to our content.
<Window x:Class="FruitSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FruitSample"
Title="Fruits"
Height="300"
Width="300">
<Window.Resources>
<local:FruitTemplateSelector x:Key="Local_FruitTemplateSelector" />
<DataTemplate x:Key="AppleTemplate">
<StackPanel Background="{Binding Color}">
<TextBlock Text="{Binding AppleType}" />
<TextBlock Text="{Binding HasWorms, StringFormat=Has Worms: {0}}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="OrangeTemplate">
<StackPanel Background="{Binding Color}">
<TextBlock Text="{Binding OrangeType}" />
<TextBlock Text="{Binding VitaminCContent, StringFormat=Has {0} % of daily Vitamin C}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<ListBox x:Name="uiFruitList"
ItemsSource="{Binding Fruits}"
DisplayMemberPath="Name" />
<ContentControl Content="{Binding Path=SelectedItem, ElementName=uiFruitList}"
ContentTemplateSelector="{StaticResource Local_FruitTemplateSelector}"/>
</DockPanel>
</Window>