WPF: How to bind object to ComboBox - wpf

Trying to learn how to bind objects to various types of controls. In this instance, I want to get sample data in my object to appear in ComboBox. The code runs but what appears instead of values (David, Helen, Joe) is text "TheProtect.UserControls.Client")
XAML: (ucDataBindingObject.xaml)
<UserControl x:Class="TheProject.UserControls.ucDataBindingObject"
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"
Width="Auto"
Height="Auto"
mc:Ignorable="d">
<Grid Width="130"
Height="240"
Margin="0">
<ComboBox Width="310"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemsSource="{Binding Path=Clients}" />
</Grid>
</UserControl>
C#: ucDataBindingObject.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
namespace TheProject.UserControls
{
public partial class ucDataBindingObject : UserControl
{
public List<Client> Clients { get; set; }
public ucDataBindingObject()
{
Clients = new List<Client>();
Clients.Add(new Client(1, "David")); // sample data
Clients.Add(new Client(2, "Helen"));
Clients.Add(new Client(3, "Joe"));
InitializeComponent();
this.DataContext = this;
}
}
C# Client.cs
using System;
using System.Linq;
namespace TheProject.UserControls
{
public class Client
{
public int ID { get; set; }
public string Name { get; set; }
public Client(int id, string name)
{
this.ID = id;
this.Name = name;
}
}
}

There are several ways to tell the framework what to display
1) Use DisplayMemberPath on the ComboBox (this will display the named property):
<ComboBox ItemsSource="{Binding Path=Clients}"
DisplayMemberPath="Name"
/>
2) Set ItemTemplate on the ComboBox. This is like #1, except allows you to define a template to display, rather than just a property:
<ComboBox ItemsSource="{Binding Path=Clients}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Green" BorderThickness="1" Padding="5">
<TextBlock Text="{Binding Path=Name,StringFormat='Name: {0}'}" />
</Border>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
3) Add a ToString() override to source class. Useful if you always want to display the same string for a given class. (Note that the default ToString() is just the class type name, which is why you see "TheProtect.UserControls.Client".)
public class Client
{
// ...
public override string ToString()
{
return string.Format("{0} ({1})", Name, ID);
}
}
4) Add a DataTemplate to the XAML resources. This is useful for associating a given class type with a more complex or stylized template.
<UserControl xmlns:local="clr-namespace:TheProject.UserControls">
<UserControl.Resources>
<DataTemplate DataType="local:Client">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</UserControl.Resources>
// ...
</UserControl>

In DisplayMemberPath, give the name of the property which you want to show in the comboBox. In SelectedValuePath, give the name of the property which you want to select. When you do a ComboBox.SelectedValue, you will get the value of this property.

Trying to get selected value from combobox returns System.Data.Entity.DynamicProxies.x
private void Button_Click(object sender, RoutedEventArgs e){
string _scanner0 = int.Parse(mycmb.SelectedValue.ToString());
string _scanner1 = mycbr.SelectedItem.ToString();
string _scanner2 = mycbr.SelectedValuePath.ToString();
string _scanner3 = mycbr.text.ToString();
}
all these Returns System.Data.Entity.DynamicProxies.x
What should i do?

Related

In WPF datatemplate, What I missed? data doesn't show correctly

I use a class 'SecondCondition' as basic data unit. It have 3 public property. ConditionColor, ConditionName, ConditionID
ObservableCollection 'SearchConditionList' is used as data list.
I made a datatemplate Binding like below.
< Model:SearchCondition x:Key="SearchCondition" />
< DataTemplate x:Key="ConditionSelector">
< StackPanel >
< xctk:ColorPicker x:Name="ConditionColorPicker"
SelectedColor="{Binding Path=ConditionColor,
Mode=TwoWay}">
< /xctk:ColorPicker>
< CheckBox x:Name="ConditionCheckbox"
Content="{Binding Path=ConditionName,
Mode=TwoWay}" />
< /StackPanel>
And I used the datatemplate at my Listbox.
< ListBox ItemsSource="{Binding Path=SearchConditionList}"
ItemTemplate="{StaticResource ConditionSelector}">
< /ListBox>
As result, I get number of blank template as much as List of items. But it doesn't show properties like color and name.
What I used as reference article use almost same and the code works, but mine is not. How can I solve this?
below is my reference.
https://learn.microsoft.com/ko-kr/dotnet/framework/wpf/data/data-templating-overview
Thank you.
P.S When I change codes like below, constant strings are shown very well but Bound value are not.
<StackPanel DataContext="{Binding Path=SearchConditionList}">
<ListBox ItemsSource="{Binding}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock Text="Why this doens't show bound value?"/>
<TextBlock Text=" : " />
<TextBlock Text="{Binding Path=ConditionName}"/>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
result is like below.
Here is my implementation based on your source code.
Model
public class SearchCondition
{
public Color ConditionColor { get; set; }
public string ConditionName { get; set; }
public string ConditionID { get; set; }
public SearchCondition(Color color, string name, string id)
{
ConditionColor = color;
ConditionName = name;
ConditionID = id;
}
}
ViewModel
public class ViewModel
{
public ObservableCollection<SearchCondition> SearchConditionList { get; set; }
public ViewModel()
{
SearchConditionList = new ObservableCollection<SearchCondition>();
SearchConditionList.Add(new SearchCondition(Colors.Red, "Red", "001"));
SearchConditionList.Add(new SearchCondition(Colors.Green, "Green", "002"));
}
}
The ViewModel is bound to the view in the code-behind.
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
Okay.. now this is the XAML.
<Window x:Class="DataBindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ConditionSelector">
<StackPanel Orientation="Horizontal">
<xctk:ColorPicker x:Name="ConditionColorPicker" SelectedColor="{Binding Path=ConditionColor, Mode=TwoWay}" />
<CheckBox x:Name="ConditionCheckbox" Content="{Binding Path=ConditionName, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Path=SearchConditionList}" ItemTemplate="{StaticResource ConditionSelector}" />
</Grid>
Screenshot
Honestly, I can't figure out where the problem is unless I see your full source code.
Visual Studio doesn't spit out exceptions explicitly. But you should be able to see the binding error in the output window. So you can find the clue.
Please compare your implementation with mine.

TwoWay Binding With ItemsControl

I'm trying to write a user control that has an ItemsControl, the ItemsTemplate of which contains a TextBox that will allow for TwoWay binding. However, I must be making a mistake somewhere in my code, because the binding only appears to work as if Mode=OneWay. This is a pretty simplified excerpt from my project, but it still contains the problem:
<UserControl x:Class="ItemsControlTest.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid>
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=.}"
x:Name="myItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Mode=TwoWay,
UpdateSourceTrigger=LostFocus,
Path=.}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Click="Button_Click"
Content="Click Here To Change Focus From ItemsControl" />
</StackPanel>
</Grid>
</UserControl>
Here's the code behind for the above control:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Collections.ObjectModel;
namespace ItemsControlTest
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public ObservableCollection<string> MyCollection
{
get { return (ObservableCollection<string>)GetValue(MyCollectionProperty); }
set { SetValue(MyCollectionProperty, value); }
}
// Using a DependencyProperty as the backing store for MyCollection. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyCollectionProperty =
DependencyProperty.Register("MyCollection",
typeof(ObservableCollection<string>),
typeof(UserControl1),
new UIPropertyMetadata(new ObservableCollection<string>()));
public UserControl1()
{
for (int i = 0; i < 6; i++)
MyCollection.Add("String " + i.ToString());
InitializeComponent();
myItemsControl.DataContext = this.MyCollection;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Insert a string after the third element of MyCollection
MyCollection.Insert(3, "Inserted Item");
// Display contents of MyCollection in a MessageBox
string str = "";
foreach (string s in MyCollection)
str += s + Environment.NewLine;
MessageBox.Show(str);
}
}
}
And finally, here's the xaml for the main window:
<Window x:Class="ItemsControlTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:ItemsControlTest"
Title="Window1" Height="300" Width="300">
<Grid>
<src:UserControl1 />
</Grid>
</Window>
Well, that's everything. I'm not sure why editing the TextBox.Text properties in the window does not seem to update the source property for the binding in the code behind, namely MyCollection. Clicking on the button pretty much causes the problem to stare me in the face;) Please help me understand where I'm going wrong.
Thanx!
Andrew
Ok I believe what is causing this problem is that you are binding directly to a String . Strings are immutable in C# and thus when you change the text, it cannot change the underlying string in the ObservableCollection. What you can do to get around this problem is simply create a model class to hold the string data, and then bind the TextBox.Text to a property inside that class. Here is an example:
public partial class BindingToString : Window
{
public BindingToString()
{
MyCollection = new ObservableCollection<TestItem>();
for (int i = 0; i < 6; i++)
MyCollection.Add(new TestItem("String " + i.ToString()));
InitializeComponent();
myItemsControl.DataContext = this.MyCollection;
}
public ObservableCollection<TestItem> MyCollection
{
get;
set;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Display contents of MyCollection in a MessageBox
string str = "";
foreach (TestItem s in MyCollection)
str += s.Name + Environment.NewLine;
MessageBox.Show(str);
}
}
public class TestItem
{
public string Name
{
get;
set;
}
public TestItem(string name)
{
Name = name;
}
}
Notice that I changed your dependency property to a standard property- there is no reason to make the collection a dependency property. Besides that the only difference is the inclusion of the wrapper class TestItem to hold the string data.
<Window x:Class="TestWpfApplication.BindingToString"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BindingToString " Height="300" Width="300">
<Grid>
<StackPanel>
<ItemsControl ItemsSource="{Binding}"
x:Name="myItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Click="Button_Click"
Content="Click Here To Change Focus From ItemsControl" />
</StackPanel>
</Grid>
Now the TextBox is bound to the Name path on TestItem, and this binding works and modifies the collection as expected.

How do I bind to something outside a datacontext

I have a listbox in WPF that is in the Layout Root.
I also have a Frame that is in the Layout Root as well.
The listbox is composed of items that have a string(Name) and a framework element(UI).
How do I bind the frame's content to be the UI property of the listbox's selected item property?
If you need a codebehind, how would you do this in MVVM
I used a ContentControl instead of Frame since I had problem binding to Content property, I never got it to refresh after binding changed. I didn't do proper MVVM, Data should not be hosted inside the view.
XAML:
<Window.Resources>
<CollectionViewSource x:Key="CVS" Source="{Binding}" />
</Window.Resources>
<StackPanel DataContext="{Binding Source={StaticResource CVS}}">
<ListBox
ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="Name">
</ListBox>
<ContentControl Content="{Binding Path=UI}" />
</StackPanel>
Code behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace BindDemo
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Data = new List<DataItem>();
Data.Add(new DataItem("TextBox", new TextBox(){ Text="hello" }));
Data.Add(new DataItem("ComboBox", new ComboBox()));
Data.Add(new DataItem("Slider", new Slider()));
DataContext = Data;
}
public List<DataItem> Data
{
get; private set;
}
}
public class DataItem
{
public DataItem(string name, FrameworkElement ui)
{
Name = name;
UI = ui;
}
public string Name { get; private set; }
public FrameworkElement UI { get; private set; }
}
}
It sounds as you want to display list of objects and details for selected object. If I am right, solution in MVVM may be following:
<ListView ItemsSource="{Binding ObjectsList}" IsSynchronizedWithCurrentItem="True" />
<ContentControl Content="{Binding ObjectsList}">
<ContentControl.ContentTemplate>
<DataTemplate>
<!-- details template -->
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>

Getting events from the WPF Checked ComboBox

Near total WPF noob. So I hooked up a combobox to have checkboxes using the following item template:
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Checked="{Binding IsSelected}"
Width="20" Name="chkDayName" Click="chkDayName_Click"/>
<TextBlock Text="{Binding DayOfWeek}"
Width="100" Name="txtDayName" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
On the actual event of a person clicking a checkbox, i catch the event in chkDayName_Click method. I have the following questions:
How do I find out values of the corresponding TextBlock in the item template?
How do i find out the index of the item that was clicked?
Is there a way to get to the parent?
Thanks.
If I understand it you want to know which combobox items are checked? You can use the chkDayName_Click for that and add the name of the day as Tag of the CheckBox. This feels very Winforms. In WPF you normally let your databinding handle functionality like this. Below is some code that will display selected item in a textbox and a list of checked weekdays.
XAML:
<Window x:Class="DayComboBoxDemo.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">
<Window.Resources>
<CollectionViewSource x:Key="checkedWeekdays" Source="{Binding Path=WeekDays}" Filter="IsCheckedFilter" />
</Window.Resources>
<StackPanel>
<ComboBox
ItemsSource="{Binding Path=WeekDays}"
SelectedItem="{Binding Path=SelectedWeekDay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox
IsChecked="{Binding Path=IsChecked}"
Width="20" Click="chkDayName_Click"/>
<TextBlock
Text="{Binding DayOfWeek}" Width="100" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Text="{Binding Path=SelectedWeekDay.DayOfWeek}" />
<ListBox
DisplayMemberPath="DayOfWeek"
ItemsSource="{Binding Source={StaticResource checkedWeekdays}}" />
</StackPanel>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
namespace DayComboBoxDemo
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List<WeekDay> weekDays = new List<WeekDay>();
foreach (DayOfWeek dayOfWeek in System.Enum.GetValues(typeof(DayOfWeek)))
{
weekDays.Add(new WeekDay() { DayOfWeek = dayOfWeek });
}
WeekDays = weekDays;
_checkedWeekdays = FindResource("checkedWeekdays") as CollectionViewSource;
DataContext = this;
}
public IEnumerable<WeekDay> WeekDays { get; set; }
public WeekDay SelectedWeekDay
{
get { return (WeekDay)GetValue(SelectedWeekDayProperty); }
set { SetValue(SelectedWeekDayProperty, value); }
}
public static readonly DependencyProperty SelectedWeekDayProperty =
DependencyProperty.Register("SelectedWeekDay",
typeof(WeekDay),
typeof(Window1),
new UIPropertyMetadata(null));
private void chkDayName_Click(object sender, RoutedEventArgs e)
{
_checkedWeekdays.View.Refresh();
}
private void IsCheckedFilter(object sender, FilterEventArgs e)
{
WeekDay weekDay = e.Item as WeekDay;
e.Accepted = weekDay.IsChecked;
}
private CollectionViewSource _checkedWeekdays;
}
public class WeekDay
{
public DayOfWeek DayOfWeek { get; set; }
public bool IsChecked { get; set; }
}
}
You can try ComboBox's SelectedIndex or SelectedValue to tell the SelectedItem. In the MVVM fashion, you can have a two-way binding between SelectedIndex and one of you ViewModel properties.

Need simple example of WPF binding Objects to Listbox with LINQ

The following example successfully binds objects with a ListBox to display them.
However, I would like to create all the objects in one class and then from another class query them with LINQ to fill my XAML ListBox, what would I need to add this example:
XAML:
<Window x:Class="WpfApplication15.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"
xmlns:local="clr-namespace:WpfApplication15">
<Window.Resources>
<ObjectDataProvider x:Key="customers" ObjectType="{x:Type local:Customers}"/>
<DataTemplate x:Key="LastNameFirst" DataType="WpfApplication15.Customer">
<StackPanel Margin="10 10 10 0" Orientation="Horizontal">
<TextBlock Text="{Binding Path=LastName}" FontWeight="bold"/>
<TextBlock Text=", "/>
<TextBlock Text="{Binding Path=FirstName}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource customers}}"
ItemTemplate="{StaticResource LastNameFirst}"/>
</Grid>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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 WpfApplication15
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Customer(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
}
public class Customers : List<Customer>
{
public Customers()
{
this.Add(new Customer("Jim", "Thompson"));
this.Add(new Customer("Julie", "Watson"));
this.Add(new Customer("John", "Walton"));
}
}
}
edit: added ToList call to the LINQ query
You can just assign the ItemsSource of the ListBox using LINQ in code-behind for this. Assuming you give your ListBox a name:
<Window x:Class="WpfApplication15.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:WpfApplication15"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="300" Height="300" Title="Window1">
<Window.Resources>
<DataTemplate x:Key="LastNameFirst" DataType="WpfApplication15.Customer">
<StackPanel Margin="10 10 10 0" Orientation="Horizontal">
<TextBlock FontWeight="bold" Text="{Binding Path=LastName}"/>
<TextBlock Text=", "/>
<TextBlock Text="{Binding Path=FirstName}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox x:Name="lstCustomers"
ItemTemplate="{StaticResource LastNameFirst}"/>
</Grid>
</Window>
You can assign to ItemsSource in the Loaded event:
public partial class Window1 : Window
{
public Window1()
{
this.Loaded += new RoutedEventHandler(Window1_Loaded);
InitializeComponent();
}
void Window1_Loaded(object sender, RoutedEventArgs e)
{
Customers customers = new Customers();
lstCustomers.ItemsSource = customers.Where(customer => customer.LastName.StartsWith("W")).ToList();
}
}
Assuming your LINQ query will change depending on some logic, you can re-assign ItemsSource at the appropriate points.
If you want to bind without dipping into code-behind whenever your query logic changes, you're probably better off using a CollectionViewSource, since it has sorting and filtering capabilities (assuming that's what you're after from using LINQ).
Take a look # http://www.codeplex.com/bindablelinq and you should find a bunch of good examples for this.

Resources