Issue Summary
When using 2 cascading comboboxes, the child combobox does not select the current item, instead an empty item (I don't know where it comes from!) gets selected.
Issue Details
I have two comboboxes declared in xaml as given below. The two entities in play are StandardRack and RelayConfig
XAML:
<ComboBox ItemsSource="{Binding StandardRacks}" DisplayMemberPath="Name"
SelectedItem="{Binding StandardRack, Mode=TwoWay}" SelectedValuePath="Id"
<ComboBox ItemsSource="{Binding RelayConfigs}" DisplayMemberPath="DisplayName"
SelectedValue="{Binding DefaultRelayConfig, Mode=TwoWay}" SelectedValuePath="Id"
Here are the backing properties, and code to load comboboxes in ViewModel
ViewModel
private ObservableCollection<StandardRack> _standardRacks;
public ObservableCollection<StandardRack> StandardRacks {
get { return _standardRacks; }
set { _standardRacks = value; RaisePropertyChanged(() => StandardRacks); }
}
private StandardRack _standardRack;
public StandardRack StandardRack {
get { return _standardRack; }
set {
if (_standardRack != value) {
_standardRack = value;
LoadRelayConfigs();
RaisePropertyChanged(() => StandardRack);
}
}
}
private ObservableCollection<RelayConfig> _relayConfigs;
public ObservableCollection<RelayConfig> RelayConfigs {
get { return _relayConfigs; }
set { _relayConfigs = value; RaisePropertyChanged(() => RelayConfigs); }
}
private RelayConfig _defaultRelayConfig;
public RelayConfig DefaultRelayConfig {
get { return _defaultRelayConfig; }
set { _defaultRelayConfig = value; RaisePropertyChanged(() => DefaultRelayConfig); }
}
private void LoadRack() {
StandardRacks = new ObservableCollection<StandardRack>(
unitOfWork.StandardRackRepository.GetQueryable().Include(sr => sr.StandardRelay).ToList());
if (StandardRacks.Count > 0) {
StandardRack = Rack.StandardRack; //Set the current value of StandardRacks combobox
}
}
//Loads RelayConfigs Combobox based on Current Value of StandardRacks Combobox
private void LoadRelayConfigs() {
RelayConfigs = new ObservableCollection<RelayConfig>(
unitOfWork.RelayConfigRepository.GetQueryable()
.Where(rc => rc.StandardRelays.Any(srl => srl.Id == StandardRack.StandardRelay.Id)).ToList());
DefaultRelayConfig = Rack.DefaultRelayConfig; //Set Current Value of RelayConfigs Combobox. Does not work.
}
The above code loads both the comboboxes (StandardRacks and RelayConfigs) items properly. However the RelayConfigs selected value is not set to the one it is pointing to from XAML. Instead I get an empty item in the RelayConfigs Combobox as the current item.
Is the 'DefaultRelayConfig' part of the 'RelayConfigs' selection?
Related
I have a ComboBox binded with an ObservableCollection. How can I do when user enter a text in the ComboBox, if item not in the list, the code automatically add a new item to the list?
<ComboBox Name="cbTypePLC"
Height="22"
ItemsSource="{StaticResource TypePLCList}"
SelectedItem="{Binding TypePLC}" IsReadOnly="False" IsEditable="True">
</ComboBox>
Bind Text property of your combo box to your view model item and then add to the bound collection there, like,
Text="{Binding UserEnteredItem, UpdateSourceTrigger=LostFocus}"
Change the UpdateSourceTrigger to LostFocus because default (PropertyChanged) will communicate each character change to your viewmodel.
// user entered value
private string mUserEnteredItem;
public string UserEnteredItem {
get {
return mUserEnteredItem;
}
set {
if (mUserEnteredItem != value) {
mUserEnteredItem = value;
TypePLCList.Add (mUserEnteredItem);
// maybe you want to set the selected item to user entered value
TypePLC = mUserEnteredItem;
}
}
}
// your selected item
private string mTypePLC;
public string TypePLC {
get {
return mTypePLC;
}
set {
if (mTypePLC != value) {
mTypePLC = value;
// notify change of TypePLC INPC
}
}
}
// your itemsource
public ObservableCollection <string> TypePLCList { set; private set;}
My Question is i want to see my artist combobox selected artist related albums in my albums combobox.
I found out code example on this site and it is similar to my problem:
but I didn't understand how we get the SelectedArtist value. I pluged this code and test it it keeps giving me null ... I tried to assign artistName to SelectedArtist from the lists of Artits I have but that wasn't successfull:
Can some one help me how i can find the SelectedArtist value before i check if its null or not
I currently have a ComboBox which is populated with Artist names and I need to bind it to another comboBox of albums once the Artist is selected.
These are set up as follows in my view:
XAML
<ComboBox Height="23" HorizontalAlignment="Left" Margin="65,81,0,0" Name="comboBox1" ItemsSource="{Binding Artists}" SelectedItem="{Binding SelectedArtist}" VerticalAlignment="Top" Width="120" />
<ComboBox Height="23" HorizontalAlignment="Left" Margin="65,115,0,0" Name="comboBox2" VerticalAlignment="Top" ItemsSource="{Binding Albums}" SelectedItem="{Binding SelectedAlbums}" Width="120" />
private void initialiseArtists()
{
MusicDataClassesDataContext dataClasses = new MusicDataClassesDataContext();
artistList = (from m in dataClasses.tblArtists select m.ArtistName).ToList();
}
public List<String> Artists
{
get
{
return this.artistList;
}
}
public List<String> Albums
{
set
{
initialiseAlbums();
}
get
{
return this.albumList;
}
}
public string SelectedArtist
{
set
{
this.selectedArtist = value;
initialiseAlbums();
}
}
private void initialiseAlbums()
{
if (selectedArtist != null)
{
MusicDataClassesDataContext dataClasses = new MusicDataClassesDataContext();
var getArtist = dataClasses.tblArtists.FirstOrDefault(band => band.ArtistName== selectedArtist);
albumList = (from album in dataClasses.tblAlbums
where album.ArtistID == getArtist.ArtistID
select album.AlbumName).ToList();
OnPropertyChanged("Albums");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));}
I would do the following:
private List<string> _artists;
public List<string> Artists
{
get { return _artists; }
set
{
_artists = value;
OnPropertyChanged("Artists");
}
}
public List<string> Albums
{
get { return GetAllAlbumsByArtist(SelectedArtist); }
}
private string _selectedArtist;
public string SelectedArtist
{
get { return _selectedArtist; }
set
{
_selectedArtist= value;
OnPropertyChanged("SelectedArtist");
OnPropertyChanged("Albums");
}
}
public List<string> GetAllAlbumsByArtist(string artist)
{
//Write your code to get the album data.
}
Then the only thing you have to do in xaml is to bind the ItensSource property of the two combo boxes correctly and set the UpdateSourceTrigger to "PropertyChanged".
There is a continuation to this question in this thread and marked it as ansawered
The best overloaded method match for 'System.Collections.ObjectModel.ObservableCollection has some invalid arguments
I've got a list of items bound to a ComboBox. When a user selects an item, I'd like to cancel the selection and select a different item instead. This must happen from within the setter of the property that the SelectedItem is bound to. I'm using Silverlight 3.
My data model for each item in the ComboBox:
public class DataItem
{
public int Id { get; set; }
public string Name { get; set; }
}
Object that is set to the DataContext:
public class DataContainer : INotifyPropertyChanged
{
public DataContainer()
{
itemList = new List<DataItem>();
itemList.Add(new DataItem() { Id = 1, Name = "First" });
itemList.Add(new DataItem() { Id = 2, Name = "Second" });
itemList.Add(new DataItem() { Id = 3, Name = "Third" });
}
public event PropertyChangedEventHandler PropertyChanged;
private DataItem selectedItem;
public DataItem SelectedItem
{
get { return selectedItem; }
set
{
if (value != null && value.Id == 2)
value = itemList[0];
selectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
private List<DataItem> itemList;
public List<DataItem> ItemList
{
get { return itemList; }
set { itemList = value; NotifyPropertyChanged("DataList"); }
}
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Relevant bits of xaml:
<StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="comboBox" DisplayMemberPath="Name" Width="100" ItemsSource="{Binding ItemList}" SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>
<Button Content="Set to First" Width="100" Click="Button_Click"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Selected item: "/>
<TextBlock Text="{Binding SelectedItem.Id}"/>
<TextBlock Text=" - "/>
<TextBlock Text="{Binding SelectedItem.Name}"/>
</StackPanel>
</StackPanel>
It looks like my code to select the first item when the user selects the second item is working. The selected item is in fact set to "First" while the ComboBox is still displaying "Second" as if it was selected.
Is there any way to force the ComboBox to redraw or to reconsider what it should visually mark as selected?
I do this from the above mentioned Button_Click method and it works:
private void Button_Click(object sender, RoutedEventArgs e)
{
var c = DataContext as DataContainer;
if (c != null)
{
c.SelectedItem = null;
c.SelectedItem = c.ItemList[0];
}
}
But setting to null and then the value I want doesn't work if I do it from within the setter like I need to.
I may have found a solution for you. I was able to get what I think you're asking for working by doing the following:
public DataItem SelectedItem
{
get { return _selectedItem; }
set
{
if (value != null && value.Id == 2)
{
value = itemList[0];
UpdateUI(); // Call this to force the UI to update.
}
_selectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
private void UpdateUI()
{
ThreadPool.QueueUserWorkItem(
o =>
{
Thread.Sleep(1);
Deployment.Current.Dispatcher.BeginInvoke(()=>
{
_selectedItem = null;
NotifyPropertyChanged("SelectedItem");
_selectedItem = itemList[0];
NotifyPropertyChanged("SelectedItem");
});
});
}
I wish I could explain to you why this is working, but I can only guess. Basically, its exiting the UI thread, and then re-entering a moment later via the Dispatcher.BeginInvoke() call. This appears to give the ComboBox control time to update itself from the user interaction, and then respond to the Dispatcher execution.
One problem I've found is that Silverlight seems to go a little wonky after multiple executions of the threading code. Increasing the Thread.Sleep time seems to help. I think this solution will work for the majority of situations and won't be an issue.
You don't have to queue a Thread with 1 second waiting as grimus suggested. this should also work for you:
public DataItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
NotifyPropertyChanged("SelectedItem");
if (value != null && value.Id == 2)
{
Diployment.Current.Dispatcher.BeginInvoke(() => {
_selectedItem = itemList[0];
NotifyPropertyChanged("SelectedItem"); });
}
}
}
Cannot figure out why is this not setting a Text property after BidAgent in ViewModel is initial set to some value? Searching and selecting works fine, but initial binding does not. Basically, what I want is when I set the view model (BidAgent) for the view, that it displays the text for the selected item that is created explicitly from the values on the BidAgent. Any ideas how to do this?
<i:Interaction.Triggers>
<i:EventTrigger EventName="AgentSearchCompleted" SourceObject="{Binding}">
<ei:CallMethodAction TargetObject="{Binding ElementName=ctlAgentSearchBox}" MethodName="PopulateComplete" />
</i:EventTrigger>
</i:Interaction.Triggers>
<sdk:AutoCompleteBox Name="ctlAgentSearchBox" Width="300" Margin="0,5,0,0" HorizontalAlignment="Left" ItemsSource="{Binding AvailableAgents}"
SelectedItem="{Binding SelectedAgent}" FilterMode="None" ValueMemberPath="SearchDisplayString" MinimumPrefixLength="1">
<sdk:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SearchDisplayString}"/>
</DataTemplate>
</sdk:AutoCompleteBox.ItemTemplate>
</sdk:AutoCompleteBox>
Code Behind
public void GetActiveAgentsByNumber(object sender, PopulatingEventArgs e)
{
e.Cancel = true;
(DataContext as BidAgentEditViewModel).GetActiveAgentsByNumber(number.ToString());
}
ViewModel
public void GetActiveAgentsByNumber(string agentNumber)
{
_bidAgentDataService.GetActiveAgentsByNumber(agentNumber, getActiveAgentsByNumberCallback);
}
private void getActiveAgentsByNumberCallback(IEnumerable<AgentSearchDto> result)
{
AvailableAgents = result;
Event.Raise(AgentSearchCompleted, this);
}
private AgentSearchDto _selectedAgent;
public AgentSearchDto SelectedAgent
{
get { return _selectedAgent; }
set
{
_selectedAgent = value;
BidAgent.AgentId = Int32.Parse(_selectedAgent.Id);
BidAgent.AgentName = _selectedAgent.FullName;
BidAgent.AgentNumber = _selectedAgent.Number;
BidAgent.AgencyName = _selectedAgent.AgencyName;
RaisePropertyChanged(()=>SelectedAgent);
}
}
private BidAgentDto _bidAgent;
public BidAgentDto BidAgent
{
get { return _bidAgent; }
private set
{
_bidAgent = value;
RaisePropertyChanged(() => BidAgent);
SelectedAgent = new AgentSearchDto()
{
Id = _bidAgent.AgentId.ToString(),
Number = _bidAgent.AgentNumber,
FullName = _bidAgent.AgentName
};
}
}
Is it possible that the object returned by the SelectedAgent property and its matching entry in the AvailableAgents property are in fact two distinct object instances that just happen to contain the same data? If so try assigning the matching instance from the AvailableAgents to the SelectedAgent once the set has been returned.
I am using a MVVM Wizard with several pages. When I set a value in the combobox and go to the next page and switch back I want to reset the value I set before.
But all whats happening is that the combobox is empty at top and the index is -1 ?
What do I wrong?
<ComboBox ItemsSource="{Binding Path=LessonNumbers}" SelectedIndex="{Binding SelectedLessonNumber}" />
private ReadOnlyCollection<int> _lessonNumbers;
public ReadOnlyCollection<int> LessonNumbers
{
get
{
if (_lessonNumbers == null)
this.CreateLessonNumbers();
return _lessonNumbers;
}
}
private void CreateLessonNumbers()
{
var list = new List<int>();
for (int i = 1; i < 24; i++)
{
list.Add(i);
}
_lessonNumbers = new ReadOnlyCollection<int>(list);
}
private int _selectedLessonNumber;
public int SelectedLessonNumber
{
get { return _selectedLessonNumber; }
set
{
if (_selectedLessonNumber == value)
return;
_selectedLessonNumber = value;
this.OnPropertyChanged("SelectedLessonNumber");
}
}
UPDATE:
<ComboBox
SelectedIndex="0"
SelectedItem="{Binding SelectedWeeklyRotationNumber}"
ItemsSource="{Binding Path=WeeklyRotationNumbers}"
Height="23"
HorizontalAlignment="Left"
Margin="336,212,0,0"
VerticalAlignment="Top"
Width="121"
MaxDropDownHeight="100"
IsReadOnly="True"
IsTextSearchEnabled="False"
/>
private ReadOnlyCollection _weeklyRotationNumbers;
public ReadOnlyCollection WeeklyRotationNumbers
{
get
{
if (_weeklyRotationNumbers == null)
this.CreateWeeklyRotationNumbers();
return _weeklyRotationNumbers;
}
}
private void CreateWeeklyRotationNumbers()
{
var list = new List<string>();
list.Add("No rotation");
for (int i = 1; i < 16; i++)
list.Add(i.ToString());
_weeklyRotationNumbers = new ReadOnlyCollection<string>(list);
}
private string _selectedWeeklyRotationNumber;
public string SelectedWeeklyRotationNumber
{
get { return _selectedWeeklyRotationNumber; }
set
{
if (_selectedWeeklyRotationNumber == value)
return;
_selectedWeeklyRotationNumber = value;
this.RaisePropertyChanged("SelectedWeeklyRotationNumber");
Messenger.Default.Send<string>(value);
}
}
Again, what do I wrong or what is wrong with the string property?
Change XAML SelectedIndex to SelectedItem:
<ComboBox ItemsSource="{Binding Path=LessonNumbers}"
SelectedItem="{Binding SelectedLessonNumber}" />
UPDATE:
Somewhere you must set the DataContext of your Window to reference the collection from your XAML.
In my case I typically do that in the constructor of my view.
// this my class containing WeeklyRotationNumbers
private MainViewModel _mvm;
public MainView()
{
InitializeComponent();
_mvm = new MainViewModel();
DataContext = _mvm;
}
I added string to the read only collections:
private ReadOnlyCollection<string> _weeklyRotationNumbers;
public ReadOnlyCollection<string> WeeklyRotationNumbers
I also implemented the interface INotifyPropertyChanged which I think you did, but you are likely using
a different base class to handle the PropertyChanged event.
Everthing else I cut and paste from your code.