MVVM Binding one ComboBox from another - c#

I am pretty new into MVVM and I wanted to ask, if you could help me getting the SelectedValue from a ComboBox to my VM-Class. In my example I have a ComboBox with all the car brands. If I select a car brand I want to have all the models from the car brand in another ComboBox to choose from. I've already made some research but I couldnt find a single solution to my problem.
After clicking on the Brand-ComboBox.
This is fine so far, but when I want to have the models displayed, the ComboBox stays empty.
Code from my View-Class:`
<ObjectDataProvider x:Key="vmcars" ObjectType="{x:Type local:VMCars}"/>
<!--MainGrid-->
<Grid DataContext="{StaticResource vmcars}">
.
.
.
.
</Grid>
Code from my Brand-ComboBox:
<!--CarBrand Combobox-->
<ComboBox x:Name="carBrand" Style="{StaticResource combobox}"
ItemsSource="{Binding AllCars}"
Grid.Column="1" Grid.Row="1"
DisplayMemberPath="c_brand"
Margin="20,13,17,15"
VerticalAlignment="Center" Height="30">
<ComboBox.SelectedItem>
<Binding Path="SelectedBrand"
BindsDirectlyToSource="True"
Mode="OneWayToSource" />
</ComboBox.SelectedItem>
</ComboBox>
Code from my Model-ComboBox:
<!--CarModel ComboBox-->
<ComboBox x:Name="carModel" Style="{StaticResource combobox}" Grid.Column="1"
Margin="20,15,17,14"
ItemsSource="{Binding ModelSelectedBrand}" DisplayMemberPath="c_model"
Grid.Row="2" VerticalAlignment="Center" Height="30">
</ComboBox>
Code from my VMClass:
public string selectedBrand;
public string SelectedBrand
{
get { return selectedBrand; }
set
{
selectedBrand = value;
PropertyChanged(this, new PropertyChangedEventArgs
("ModelSelectedBrand"));
}
}
public IEnumerable<c_car> ModelSelectedBrand
{
get
{
return (from c in db.c_car
where c.c_brand.Equals(SelectedBrand) //THIS IS CAUSING PROBLEMS
select c).GroupBy(x=>x.c_model).Select(x=>x.FirstOrDefault()).OrderBy(x=>x.c_model).ToList();
}
}
When I then comment the WHERE clause in my Linq statement, I get this result:
click here
These are all the car models.
I think the problem is that my "SelectedBrand" Property doesnt get any value from the carBrand-ComboBox
Therefore, I wanted to ask you, if anybody has an idea what might cause the trouble.

Your car brand ComboBox is currently passing the class name of c.car to your query. If you bind to SelectedValue instead of SelectedItem, and set SelectedValuePath to "c_car" it works.
So your new CarBrand ComboBox definition will be
<!--CarBrand Combobox-->
<ComboBox x:Name="carBrand" Style="{StaticResource combobox}"
ItemsSource="{Binding AllCars}"
Grid.Column="1" Grid.Row="1"
DisplayMemberPath="c_brand"
SelectedValuePath="c_brand"
Margin="20,13,17,15"
VerticalAlignment="Center" Height="30">
<ComboBox.SelectedValue>
<Binding Path="SelectedBrand"
BindsDirectlyToSource="True"
Mode="OneWayToSource" />
</ComboBox.SelectedValue>
</ComboBox>

Related

How to access selectedItems of a ListBox from ShellViewModel.cs file?

So here is my problem:
I want to access the items (and their properties), which are bound to a ListBox via a Bindable Collection, in a method of another class. (ShellViewModel)
More precise: I want to know which Items in this List are currently selected.
I'm using C#,Caliburn.Micro and the design pattern should be MVVM.
If the Button "Evaluate" is pressed, according to the selected ListBoxItems, different Methods get called.
So the XAML-Code in the ShellView is working fine, and also the "functional" code "works", but it is just working if I code directly in the xaml.cs-file. (big no,no)
Because I'm quite new to this topics, I've tried to create a new instance of the View for the access, but this will just give me control over the ListBox without "knowing" which Items are selected in the original instance.
Here is the general ListBox
<ListBox x:Name="MainListBox" ItemsSource="{Binding SCollection}"
Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="6" Grid.RowSpan="1"
SelectionMode="Multiple"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
MaxHeight="300" MinHeight="200" MaxWidth="340" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="0,5">
<StackPanel>
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" Padding="3,5,5,5" />
<TextBlock Text="{Binding Path=Info}" FontWeight="SemiBold" Padding="3,5,5,5" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="Evaluate" Background="Blue" Grid.Column="1" Grid.Row="7" MinWidth="80" Margin="5" Content="Test" Grid.ColumnSpan="1" />
So here are some questions:
Is there a similar solution like the binding of "SelectedItem" via xaml, which is bound to accessible variable, for multiple selected Items?(maybe I just missed something there?)
Is there a way to access the "original" instance of the ListBox(and therefore "live"-selected Items)?
To be clear:
Only problem, besides maybe other better solutions out there, is that, I want the called Method "Evaluate", which is bound to the button, have access to the great knowledge of which Items are selected in my ListBox.
I believe I understood your concern correctly. For the Evaluate method to have the knowledge of which Items has been selected, you can adopt the following strategy.
Add a boolean property in your Collection Type called IsSelected, which would help in tracking the items which are selected. For example,
public class Contact : PropertyChangedBase
{
public string Name { get; set; }
public string Info { get; set; }
public bool IsSelected { get; set; }
}
Now assuming your Collection is defined as following.
public ObservableCollection SCollection { get; set; } = new ObservableCollection();
You can filter the Selected items in Evaluate Method as following.
public void Evaluate()
{
var selected = SCollection.Where(x => x.IsSelected);
}
Update
You would need to Set the IsSelected Property to true during the selection in the List box. This can be done as following.
<ListBox x:Name="MainListBox" ItemsSource="{Binding SCollection}"
SelectionMode="Multiple"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
MaxHeight="300" MinHeight="200" MaxWidth="340" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="0,5">
<StackPanel>
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" Padding="3,5,5,5" />
<TextBlock Text="{Binding Path=Info}" FontWeight="SemiBold" Padding="3,5,5,5" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Example Screenshot

How to fill WPF combobox with text & image at runtime

I am trying to copy a WinForms application using WPF.
In my application I have a ComboBox that is filled with the titles of movies, to let me search the movies. Now that I'm porting it to WPF, I want to use the ComboBox to display the title and poster image of the movie.
I have already created the ComboBox and the two objects, but don't know how to populate the ComboBox at runtime, because the Add method doesn't let me add the two objects at one.
combobox.items.add()
Please help me find out how to fill the ComboBox with text and images at runtime.
update: this is the xaml code for the combobox
<ComboBox x:Name="busca_pelicula" HorizontalAlignment="Left" VerticalAlignment="Top" Width="334" Margin="147,93,0,0" IsEditable="True">
<ComboBox.Effect>
<DropShadowEffect/>
</ComboBox.Effect>
<TextBox HorizontalAlignment="Left" VerticalAlignment="Center" Name="PeliText"/>
<Image HorizontalAlignment="Right" Height="50" VerticalAlignment="Center" Width="50"/>
</ComboBox>
and the code for fill the combobox with my data from my movie database is this
busca_pelicula.Items.clear();
using sql stuff to retrieve title and movie poster
BitmapImage ImagenPoster = new BitmapImage();
ImagenPoster.BeginInit();
ImagenPoster.UriSource = new Uri(wMediaPath+"Peli\\"+wNombrePoster);
ImagenPoster.EndInit();
busca_pelicula.Items.Add(? don't know what to put here)
Firstly you have to create a class similar to this:
public class Movie : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name { get; set; }
public ImageSource Picture { get; set; }
}
Then in your xaml you have to change the combo as follow:
<ComboBox >
<ComboBox.ItemTemplate>
<ItemContainerTemplate>
<StackPanel>
<Image Source="{Binding Picture}"></Image>
<TextBlock ><Run Text="{Binding Name}"/></TextBlock>
</StackPanel>
</ItemContainerTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
this is the way to declare the wpf combobox with uge amount of data to let you "virtualize" the render
<ComboBox x:Name="busca_pelicula" HorizontalAlignment="Left" VerticalAlignment="Top" Width="334" Margin="147,93,0,0" IsEditable="True" Height="55" SelectionChanged="busca_pelicula_SelectionChanged" >
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.Effect>
<DropShadowEffect/>
</ComboBox.Effect>
<ComboBox.ItemTemplate>
<ItemContainerTemplate>
<StackPanel>
<TextBlock ><Run Text="{Binding Name}" /></TextBlock>
<Image Source="{Binding Picture}" Width="50" Height="50" HorizontalAlignment="Right"></Image>
</StackPanel>
</ItemContainerTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
now is working like a charm, the combobox only renders the 10 items that it is showing, hope that helps some one.

Can't bind ObservableCollection to Combobox in wpf

I have the following xaml:
<UserControl.Resources>
<sivm:Locator x:Key="viewModelLocator" ModelType="{x:Type ViewModels:RateVSConcentrationViewModel}"/>
</UserControl.Resources>
<UserControl.DataContext>
<Binding Path="ViewModel" Source="{StaticResource viewModelLocator}"/>
</UserControl.DataContext>
...
<ComboBox Grid.Column="0" Grid.Row="1" Height="20" VerticalAlignment="Top" ItemsSource="{Binding Chambers}" > <!--SelectedItem="{Binding SelectedChamber}">-->
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ChamberName}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Chambers is an ObservableCollection of objects that contains a property called ChamberName which I want displayed in the combobox.
In the ViewModel I have the following code:
foreach (var chamber in chambers)
{
Chambers.Add(chamber);
}
OnPropertyChanged(() => Chambers);
But when this code executes the combobox is not updated. I have used this way to do a lot of databinding but I can't get this to work.
Can someone see what I am doing wrong here?
You may need to set the relativesource on your textblock, try modifying the binding on the Text property of your textblock to the following:
{Binding DataContext.ChamberName, RelativeSource={RelativeSource AncestorType=ComboBox}}
And as nit said above, always check for binding errors, they will put you on the right path.
You should adjust the property binding as follows:
<ComboBox ItemsSource="{Binding Path=ViewModel.Chambers, Source={StaticResource viewModelLocator}}" >
I got it to work doing this:
<ComboBox DisplayMemberPath="ChamberName" Grid.Column="0" Grid.Row="1" Height="20" VerticalAlignment="Top" ItemsSource="{Binding Chambers}"> <!--SelectedItem="{Binding SelectedChamber}">-->

Adding other items to Custom ListBox/ListView and how can user add their own items

Trying to learn WPF and it's driving me nuts. So I'm trying to do something like the picture that I posted here. But I can only get the combobox, text, checkbox on top of each other and not side by side... How can I do this?
Also I figured out how to add "text" to the listbox if the user pushes a button, but I need it so that when they add text from a textbox. The combobox, checkbox, and button get added with it. But I have no idea how to do that.
I'm assuming I have to make a class for those things. But how do I do that if I'm coding it in XAML? This WPF is confusing for me.
<ListBox HorizontalAlignment="Left" Height="195" Margin="25,345,0,0" VerticalAlignment="Top" Width="650">
<ListBoxItem>
<StackPanel Orientation="Horizontal" Height="45"> <!--Stacks Items Horizontally-->
<ComboBox Width="100" Height="30">
<ComboBoxItem IsSelected="True">DirecTV</ComboBoxItem>
<ComboBoxItem>Hyundai</ComboBoxItem>
<ComboBoxItem>None</ComboBoxItem>
</ComboBox>
<TextBox Width="445" Height="30" Text="Follow RedZone on Twitter" VerticalContentAlignment="Center"/>
<CheckBox IsChecked="True" VerticalAlignment="Center">
<CheckBox.LayoutTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5"></ScaleTransform>
</CheckBox.LayoutTransform>
</CheckBox>
<Button Content="Delete" Height="Auto" Width="Auto" HorizontalAlignment="Right" VerticalAlignment="Top" VerticalContentAlignment="Top"/>
</StackPanel>
</ListBoxItem>
</ListBox>
Define a class to hold the items
public class myListBoxRow
{
public string comboBoxSelection {get;set;}
public string textBlockText {get;set;}
public bool checkBoxChecked {get;set;}
}
Now define a ObservableCollection or List somewhere (typically in a ViewModel)
public ObservableCollection myListBoxRows<myListBoxRow> {get;set}
Then bind the ListBox's ItemsSource to your collection
<ListBox ItemsSource="{Binding myListBoxRows}" .../>
To get the controls you want, define an ItemTemplate for your ListBox
<ListBox ItemsSource="{Binding myListBoxRows}" ...>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding cboItems}" Width="50" SelectedItem="{Binding comboBoxSelection}" />
<TextBlock Text="{Binding textBlockText}"></TextBlock>
<CheckBox IsChecked="{Binding checkBoxChecked}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Research MVVM in WPF as there are tons of examples of this everywhere. The key part that will tie that together for you is the datatemplate for your listbox/listview

SelectedIndex with OneWayToSource binding does not trigger

I have a problem with a particular xaml databinding.
I have two listboxes (master-details, so the listboxes have IsSynchronizedWithCurrentItem set to true). I want my viewmodel to know when the selected item on the details listbox changes: I created an int property on my viewmodel class (i.e. we can call this property SelInd)and on the details viewmodel I bind this way:
SelectedIndex="{Binding Mode=OneWayToSource, Path=SelInd}"
I get no errors/exceptions at runtime, but the binding does not trigger: my viewmodel's property does not get updated when the selected item changes. If I change the binding mode to TwoWay everything works fine, but that's not what I need. I need it to work with OneWayToSource (btw the same non-working behaviour applies if I bind SelectedItem to SelectedValue properties).
Why do those bindings do not trigger with OneWayToSource?
Here's a more complete code example, just to get the things clearer:
EDIT: I can't show the real code (NDA) but I'll show here something simpler and similar enough (the Page's DataContext is an instance of the PageViewModel class explained later)
I just need that my viewmodel class's SelInd property should always reflect the value of SelectedIndex in the second ListBox. I have found alternative methods for doing this (Event handler in code-behind or an Attached Behaviour) but right now I'm just curious about WHY it doesn't work with OneWayToSource binding.
<Page>
<ContentControl x:Name="MainDataContext">
<Grid DataContext={Binding Path=Masters}>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0"
SelectionMode="Single"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding }">
<ListBox.ItemContainerStyle>
...
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
....
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Grid.Column="1"
SelectionMode="Single"
SelectedIndex="{Binding Mode=OneWayToSource, ElementName=MainDataContext,Path=DataContext.SelInd}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Details}">
<ListBox.ItemContainerStyle>
...
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
....
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</ContentControl>
</Page>
Here's a sketch of the view model class
public class PageViewModel{
public ObservableCollection<MasterClass> Masters {get;set;}
public int SelInd {get;set;}
....
}
And here's MasterClass, it just holds a name and a list of details
public class MasterClass{
public ObservableCollection<DetailsClass> Details {get;set;}
public String MasterName {get;set;}
....
}
I think in your case, you must use the mode OneWay. By default, you have used mode TwoWay.
Quote from MSDN about TwoWay:
TwoWay binding causes changes to either the source property or the target property to automatically update the other. This type of binding is appropriate for editable forms or other fully-interactive UI scenarios. Most properties default to OneWay binding, but some dependency properties (typically properties of user-editable controls such as the Text property of TextBox and the IsChecked property of CheckBox) default to TwoWay binding. A programmatic way to determine whether a dependency property binds one-way or two-way by default is to get the property metadata of the property using GetMetadata and then check the Boolean value of the BindsTwoWayByDefault property.
Mode OneWay, that you need:
OneWay binding causes changes to the source property to automatically update the target property, but changes to the target property are not propagated back to the source property. This type of binding is appropriate if the control being bound is implicitly read-only. For instance, you may bind to a source such as a stock ticker or perhaps your target property has no control interface provided for making changes, such as a data-bound background color of a table. If there is no need to monitor the changes of the target property, using the OneWay binding mode avoids the overhead of the TwoWay binding mode.
Mode OneWayToSource:
OneWayToSource is the reverse of OneWay binding; it updates the source property when the target property changes. One example scenario is if you only need to re-evaluate the source value from the UI.
Below is a diagram for a better understanding of the:
Okay, then I'll show you an example that works for me. Perhaps it will be useful to you.
XAML
<Window x:Class="SelectedIndexHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SelectedIndexHelp"
Title="MainWindow" Height="350" Width="525"
ContentRendered="Window_ContentRendered"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<local:SelectedIndexClass x:Key="SelectedIndexClass" />
</Window.Resources>
<Grid DataContext="{StaticResource SelectedIndexClass}">
<ListBox x:Name="MyListBox"
BorderThickness="1"
Width="200" Height="200"
BorderBrush="#CE5E48"
DisplayMemberPath="Name"
Background="AliceBlue"
SelectedIndex="{Binding MySelectedIndex, Mode=OneWayToSource}" />
<Label Name="SelectedIndex" VerticalAlignment="Top"
Content="{Binding MySelectedIndex}"
ContentStringFormat="SelectedIndex: {0}"
Width="100" Height="30" Background="Lavender" />
</Grid>
</Window>
Code behind
public partial class MainWindow : Window
{
public class Person
{
public string Name
{
get;
set;
}
public int Age
{
get;
set;
}
}
private ObservableCollection<Person> DataForListBox = new ObservableCollection<Person>();
public MainWindow()
{
InitializeComponent();
}
private void Window_ContentRendered(object sender, EventArgs e)
{
DataForListBox.Add(new Person()
{
Name = "Sam",
Age = 22,
});
DataForListBox.Add(new Person()
{
Name = "Nick",
Age = 21,
});
DataForListBox.Add(new Person()
{
Name = "Cris",
Age = 25,
});
DataForListBox.Add(new Person()
{
Name = "Josh",
Age = 36,
});
DataForListBox.Add(new Person()
{
Name = "Max",
Age = 32,
});
DataForListBox.Add(new Person()
{
Name = "John",
Age = 40,
});
MyListBox.ItemsSource = DataForListBox;
MyListBox.Focus();
}
}
public class SelectedIndexClass
{
private int? mySelectedIndex = 0;
public int? MySelectedIndex
{
get
{
return mySelectedIndex;
}
set
{
mySelectedIndex = value;
}
}
}
Output
In this example, there is a class of data - Person, these data for ListBox. And the class SelectedIndexClass (DataContext), which contains the property MySelectedIndex, which is a parameter of binding OneWayToSource.
Edit: I'm glad you figured out with the problem. I'll try to explain by their example, why are you not working with ElementName case.
So, let's say we have this code:
<ContentControl x:Name="MainDataContext">
<Grid x:Name="MainGrid" DataContext="{StaticResource SelectedIndexClass}">
<ListBox x:Name="MyListBox"
BorderThickness="1"
Width="200" Height="200"
BorderBrush="#CE5E48"
DisplayMemberPath="Name"
Background="AliceBlue"
SelectedIndex="{Binding Path=DataContext.MySelectedIndex, Mode=OneWayToSource, ElementName=MainDataContext}" />
<Label Name="SelectedIndex" VerticalAlignment="Top"
Content="{Binding MySelectedIndex}"
ContentStringFormat="SelectedIndex: {0}"
Width="100" Height="30" Background="Lavender" />
</Grid>
</ContentControl>
As you probably understand, it will not work.
DataContext set on a specific node of the visual tree, all items below (in the visual tree) inherit it. This means that the DataContext will be working since the Grid and below the visual tree. Therefore, the following code will work:
<ContentControl x:Name="MainDataContext">
<Grid x:Name="MainGrid" DataContext="{StaticResource SelectedIndexClass}">
<ListBox x:Name="MyListBox"
BorderThickness="1"
Width="200" Height="200"
BorderBrush="#CE5E48"
DisplayMemberPath="Name"
Background="AliceBlue"
SelectedIndex="{Binding Path=DataContext.MySelectedIndex, Mode=OneWayToSource, ElementName=MainGrid}" />
<Label Name="SelectedIndex" VerticalAlignment="Top"
Content="{Binding MySelectedIndex}"
ContentStringFormat="SelectedIndex: {0}"
Width="100" Height="30" Background="Lavender" />
</Grid>
</ContentControl>
And also, it will work if the name of the point MyListBox. Usually, when set the DataContext, the element name is passed.
Well, I found a way to make it work. I just removed the data-context "indirection" so I don't have to use ElementName in my bindings, and it started working. The working xaml example is:
<Page>
<ContentControl >
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0"
SelectionMode="Single"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Masters }">
<ListBox.ItemContainerStyle>
...
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
....
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Grid.Column="1"
SelectionMode="Single"
SelectedIndex="{Binding Mode=OneWayToSource, Path=SelInd}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Masters/Details}">
<ListBox.ItemContainerStyle>
...
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
....
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</ContentControl>
</Page>
Now, if someone knows exactly WHY the binding using ElementName does not work, I'd like to know it :)

Categories