WPF Databinding with a ComboBox - c#

I'm trying to learn WPF Databinding with Entity Framework. I have implemented the tutorial in the link
and it works perfectly fine. I'm trying to insert a combo box myself and I want to bind it to category's name. But I couldn't achieve it.
Here is my attempt :
on XAML file :
<ComboBox HorizontalAlignment="Left" Margin="394,421,0,0" VerticalAlignment="Top" Width="120" Name="ComboCategory" Binding="{Binding Name}" />
and code-behind :
ComboCategory.ItemSource = _context.Categories.Local.ToList();
Can you tell me what I'm missing? Thanks.

Altough the use of the ItemSource is perfectly valid. I suggest you work with Data Binding. Here's a nice definition from MSDN:
Data binding is the process that establishes a connection between the application UI and business logic. If the binding has the correct settings and the data provides the proper notifications, then, when the data changes its value, the elements that are bound to the data reflect changes automatically. Data binding can also mean that if an outer representation of the data in an element changes, then the underlying data can be automatically updated to reflect the change. For example, if the user edits the value in a TextBox element, the underlying data value is automatically updated to reflect that change.
I have answered a question where someone also had a problem with binding items to a ListBox. This is not a ComboBox but the principle is the same. Click here to go the question, and here to go straight to the answer.
Basically it comes down to this:
Set up your UI
Bind your data
In following code, I changed the properties a bit according to the properties used in the tutorial.
XAML:
<ListBox Margin="20" ItemsSource="{Binding Products}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=ProductId}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
C#
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
}
public class ProductViewModel
{
public List<Product> Products
{
get
{
return new List<Product>
{
new Product{ ProductId = 1, Name = "Product_1" },
new Product{ ProductId = 2, Name = "Product_2" }
};
}
}
}
//Following code can be placed in the Loaded event of the page:
DataContext = new ProductViewModel();

You are missing DisplayMemberpath property here
<ComboBox HorizontalAlignment="Left" Margin="394,421,0,0" VerticalAlignment="Top" Width="120" Name="ComboCategory" DisplayMemberPath = "Name" />

It worked when I used this in XAML:
<ComboBox HorizontalAlignment="Left" Margin="394,421,0,0" VerticalAlignment="Top" Width="120" Name="ComboCategory" DisplayMemberPath = "Name" ItemsSource="{Binding}" />

Couldnt believe it, check the link
The issue is ItemsSource(plural) not ItemSource(singular)

Related

WPF DataGridComboBoxColumn TextBinding not updating

I am attempting to use a DataGridComboBoxColumn but I'm having trouble with displaying the text associated with the Selected Item. Technically, I'm using MaterialDataGridComboBoxColumn which extends DataGridComboBoxColumn (see that code here. The only real difference seems to be the addition of ItemSourceBinding which makes binding to a non static list easier.)
Here is the ComboBox in xaml:
<materialDesign:MaterialDataGridComboBoxColumn
Header="Meter"
ElementStyle="{StaticResource CenterEverything}"
ItemsSourceBinding="{Binding PotentialMeters}"
DisplayMemberPath="Name"
TextBinding="{Binding Segment.Meter.Name, Mode=OneWay}"
SelectedItemBinding="{Binding Segment.Meter, UpdateSourceTrigger=PropertyChanged}" />
The item source for the DataGrid is made of an ObservableCollection consisting of SegmentWrappers:
public sealed class SegmentWrapper : INotifyPropertyChanged {
public Segment Segment { get; set; }
public List<Meter> PotentialMeters => GetPotentialMeters();
public event PropertyChangedEventHandler PropertyChanged;
private List<Meter> GetPotentialMeters() => Segment.Station.AllMeters;
}
The Segment object is my actual model:
public sealed class Segment : INotifyPropertyChanged {
public Station Station { get; set; }
public Meter Meter { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
Now, the weird thing is I can get this to work sometimes when I'm running in debug. Normally, when I start up I cannot not see the name of the selected Meter in the ComboBox, but if I remove and re-add the TextBinding attribute from the xaml above while the code is running, the name is displayed correctly! I guess this has something to do with the UpdateSourceTrigger, but I cannot figure it out. Any ideas?
In the setter of your segment propeties, notify the property to reflect in the UI
I have a workaround for this now (so perhaps this should not be considered an answer? I'm new to SO, so I will happily post this as a comment if it should not be an answer).
I switched over to a DataGridTemplateColumn and basically used the same binding for everything and it worked. I changed my original xaml to this:
<DataGridTemplateColumn Header="Meter" Width="Auto" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Style="{StaticResource MaterialDesignDataGridComboBox}"
Foreground="{DynamicResource MaterialDesignBody}"
ItemsSource="{Binding PotentialMeters, Mode=OneWay}"
DisplayMemberPath="Name"
Text="{Binding Segment.Meter.Name, Mode=OneWay}"
SelectedItem="{Binding Segment.Meter}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I have no real answer as to why the original approach didn't work, but I'm happy with this.

WPF Data Binding an ObservableCollection (or LinkedList) to a WrapPanel

Ok. First of all, I've looked at a bunch of other questions and a ton of other places on the internet on how to do this, and none of them are helping, so please don't mark this as a duplicate, and also, heads up cause I probably made a really stupid mistake.
I'm trying to bind an ObservableCollection to a WrapPanel using an ItemsControl and a DataTemplate. The following is my XAML Code:
<ItemsControl x:Name="wPanel">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--<Border BorderBrush="DarkGray" Background="Transparent">-->
<StackPanel MinWidth="250">
<Grid>
<TextBlock Text="{Binding address}" />
</Grid>
<Grid>
</Grid>
</StackPanel>
<!--</Border>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Note: I did have (for the ItemsSource property of ItemsControl) {Binding properties}
This is my declaration of properties:
public ObservableCollection<Property> properties = new ObservableCollection<Property>();
And the Property Class is the following plus many more properties:
private string address { get; set; }
private string city { get; set; }
private string postcode { get; set; }
private double price { get; set; }
private LinkedList<Tennant> tennants { get; set; }
...
I thought I had solved the problem with this,
Binding binding = new Binding();
binding.Source = properties;
wPanel.SetBinding(ItemsControl.ItemsSourceProperty, binding);
But, then the line in the xaml: <TextBlock Text="{Binding address}" /> didn't work.
Then I came to the conclusion that it had to do with the properties object, and how it wouldn't bind unless I did it through code.
What am I doing wrong, for it not bind through XAML, etc.? What do I need to change about the properties object, or what do I need to do?
Thanks in advance.
You can bind ItemsControl's ItemsSource to properties just like #AjS answer. But before that, you need to change properties declaration to be property instead of field.
public ObservableCollection<Property> properties { get; set; }
And also address property of your Property class need to be public.
public string address { get; set; }
Isn't the 'properties' a property on the window?
If you are binding in xaml, make sure you use declare 'properties' as 'Property', set datacontext of window to itself and then set binding path:-
<ItemsControl x:Name="wPanel" ItemsSource="{Binding properties}">
this.DataContext=this; //set datacontext on parent window or control
If you are doing it in code, setting the ItemsSource directly on wPanel should work:-
wPanel.ItemsSource=properties;
This is easy trick for check Binding. Every WPF developer must know this. For example:
<TextBlock Text="{Binding}" />
Run and see it.
If TextBlock has any object in DataContext, object class name displayed in TextBlock.
If DataContext has empty. TextBlock is Empty

Having trouble binding ViewModel to ComboBox

I have a viewmodel setup as the following
public class cDriveListVM
{
public string Drive { get; set; }
public cDriveListVM(string name)
{
Drive = name;
}
}
I declare the observablecollection in the window and set its datacontext to this observable collection.
public ObservableCollection<cDriveListVM> DriveList { get; set; }
private void dl()
{
DriveList = new ObservableCollection<cDriveListVM>();
DriveList.Add(new cDriveListVM("drive 1"));
DriveList.Add(new cDriveListVM("drive 2"));
this.DataContext = DriveList;
}
Xml for combobox:
<ComboBox x:Name="Drive_ComboBox" ItemsSource="{Binding Path=Drive}" HorizontalAlignment="Center" IsReadOnly="True" Grid.Column="0" Grid.Row="0" Width="300" Margin="10" SelectionChanged="Drive_Changed" Height="22" VerticalAlignment="Top"/>
I am just learning how to use Viewmodel so I am unsure what I am doing wrong, any help would be appreciated. I updated the xml file it results in the following combbox.
There are a few problems with this code.
One, the binding is set up wrong. Since the property with the viewmodel collection is DriveList, the binding should be ItemsSource="{Binding Path=DriveList}".
Two, you are attempting to display a field from your viewmodel, which is not doable. WPF's binding engine only works with properties, so the viewmodel should have a property:
public string Drive { get; set; }
And finally, the DisplayMemberPath should match the property name from the viewmodel: DisplayMemberPath="Drive".
Update: I just noticed that the DataContext is the observable collection itself -- I probably missed it on the first read. In that case, you want to bind directly to the data context:
ItemsSource="{Binding}"
And set DisplayMemberPath to the property you want to display:
DisplayMemberPath="Drive"

WinRT XAML data binding a List of objects that contain a List

I have a List of objects that contain another List. I want to bind both Lists to different controls (one nested within the other - a ListView as GridViewItem). But I can't get the xaml to work.
Very close to this question comes Binding List of Lists in XAML?.
And there is an article regarding this in the MSDN documentation:
How to bind to hierarchical data and create a master/details view - could be the solution, but I find it difficult to apply it to my code.
Other articles touch this topic, but not that good and also as a new user I'm not allowed to include more than two hyperlinks in a question.
My code looks similar to this (changed to city / restaurant scenario for clarity):
Model:
public class City
{
string Name { get; set; }
List<Restaurant> RestaurantList { get; set; }
//.. also a constructor with parameters for the properties and an overriding toString method that returns Name
}
public class Restaurant
{
string Name { get; set; }
List<Uri> UriList { get; set; }
//.. also a constructor with parameters for the properties and an overriding toString method that returns Name
}
Code-behind (LoadState method):
//.. getting a List of cities (with restaurants), that is being created in some model class
this.DefaultViewModel["Items"] = Cities;
Some people set the DataContext instead. I got this from the MSDN tutorials and it worked so far. But I'm not sure which is "better".
Okay now the XAML:
I want to have a GridView with the Cities as GridViewItems. Within one GridViewItem there's a Grid, displaying the City's Name in the top row and a ListView below. The ListView contains the Restaurants (only of that City!). The ListViewItems are only TextBlocks showing the Restaurant's Name.
I want only the Restaurants to be clickable.
Like this:
<!-- the following line is at the very top and the reason why it should work without setting DataContext explicitly -->
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
<!-- ... -->
<GridView Grid.Row="1" ItemsSource="{Binding Items}" SelectionMode="None">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Height="500" Width="200" Margin="50" Background="Gray">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="5*"/>
</Grid.RowDefinitions>
<TextBlock TextWrapping="Wrap" Text="{Binding Name}"/>
<ListView
Grid.Row="1"
ItemsSource="{Binding RestaurantList}" IsItemClickEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Tapped="Restaurant_Click"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
This way only gray boxes are shown. When changing the TextBlock's binding to Text="{Binding}" then at least the Names of the Cities are shown. Which I don't understand and also don't want, because I guess the overriding of the toString methods is not meant to be used this way. The Names of the Restaurants don't show up in both cases.
Also, the scrolling somehow broke in this view, but that's a different story I suppose.
So: What's wrong with the data binding in the XAML?
The databinding engine needs public properties (the link is about WPF but the same concepts apply in WinRT):
You can bind to public properties, sub-properties, as well as
indexers, of any common language runtime (CLR) object.
But if you don't specify it explicit the compiler treats members by default "the most restricted access you could declare for that member" e.g. private in your case.
So you need to declare your properties as public:
public class City
{
public string Name { get; set; }
public List<Restaurant> RestaurantList { get; set; }
}
public class Restaurant
{
public string Name { get; set; }
public List<Uri> UriList { get; set; }
}

How to choose which ObservableCollection to bind to a listbox?

Is that even possible? I have two ObservableCollections. I want to bind and populate a listbox with one of them. For example let's say that we have 2 buttons - one for Twitter and one for Facebook. Clicking on a Facebook button it will populate listbox with friend's names from facebook observable collection and it will bind it. Clicking on Twitter it will populate listbox with Twitter followers and populate listbox and bind it.
How to choose which collection will be populated in listbox?
I would just use one observable collection and fill based on the users choice. You could also fill it with the names from both sources and have a filter to filter out one or the other (apparently you need a wrapper object where you can indicate whether the name is a facebook friend or twitter follower).
Edit: Here is some quick code example of how you can do it:
public interface ISocialContact
{
string Name { get; }
}
public class FacebookContact : ISocialContact
{
public string Name { get; set; }
public string FacebookPage { get; set; }
}
public class TwitterContact : ISocialContact
{
public string Name { get; set; }
public string TwitterAccount { get; set; }
}
Then in your data context:
public ObservableCollection<ISocialContact> Contacts { get; set; }
...
Contacts = new ObservableCollection<ISocialContact> {
new FacebookContact { Name = "Face", FacebookPage = "book" },
new TwitterContact { Name = "Twit", TwitterAccount = "ter" }
};
And in your xaml:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:FacebookContact}">
<TextBlock Text="{Binding FacebookPage}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:TwitterContact}">
<TextBlock Text="{Binding TwitterAccount}"/>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Contacts}" Width="100" Height="100"/>
</Grid>
This will apply the appropriate template to each object in your collection. So you can have collection with just facebook contacts or just twitter contacts or mixed.
Also note: You do not need the common interface. It will also work if you just make your ObservableCollection of type object. But given that they are being displayed by the same app in the same list box indicates that you can find some kind of common base and either can create a comon interface or base class.
In your ViewModel, create a property that exposes one or the other ObservableCollection, and swap it out when the button is clicked:
private ObservableCollection<string> _twitterFriendList;
private ObservableCollection<string> _facebookFriendList;
private ObservableCollection<string> _selectedFriendList;
public ObservableCollection<string> SelectedFriendList
{
get { return _selectedFriendList; }
set
{
if (value != _selectedFriendList)
{
_selectedFriendList = value;
RaisePropertyChanged("SelectedFriendList");
}
}
}
void TwitterButton_Click(object sender, RoutedEventArgs e)
{
SelectedFriendList = _twitterFriendList;
}
void FacebookButton_Click(object sender, RoutedEventArgs e)
{
SelectedFriendList = _facebookFriendList;
}
Then in your XAML you can just bind to the property:
<ListBox ItemsSource="{Binding SelectedFriendList}"/>
A non-elegant way of accomplishing this is to put 2 listboxes in the same location and bind 1 to the twitter collection and the other to the facebook collection. Bind their visibility to a property that changes based upon the button clicks. Personally, I'd have 2 radio buttons and display the listbox based upon which one is selected.
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=TwitterCollection}" SelectedItem="{Binding Path=SelectedTwitterItem}" Visibility="{Binding Path=IsTwitterSelected, Converter={StaticResource visibilityConverter}}" />
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=FacebookCollection}" SelectedItem="{Binding Path=SelectedFacebookItem}" Visibility="{Binding Path=IsFacebookSelected, Converter={StaticResource visibilityConverter}}" />
<RadioButton Grid.Row="1" Grid.Column="0" GroupName="rdoOptions" Content="{Binding Path=TwitterLabel}" IsChecked="{Binding Path=IsTwitterSelected}" />
<RadioButton Grid.Row="1" Grid.Column="1" GroupName="rdoOptions" Content="{Binding Path=FacebookLabel}" IsChecked="{Binding Path=IsFacebookSelected}" />

Categories