I have a SL ComboBox like the following:
<ComboBox ItemsSource="{Binding UserList}" DisplayMemberPath="Name" />
where UserLists is:
List<UserItem>
and each UserItem is:
public class UserItem
{
public int Code { get; set; }
public string Name { get; set; }
}
Since ItemsSource Property is set by Binding, how is it possible to set SelectedIndex property to zero? When I try to set this property, I have an index out of range exception.
My goal is to set as selected the first item of UserList.
Thank you in advance.
Make your UserList a dependency property and use the PropertyChangedCallback option in DependencyProperty.Register().
public ObservableCollection<UserItem> UserList
{
get { return (ObservableCollection<UserItem>)GetValue(UserListProperty); }
set { SetValue(UserListProperty, value); }
}
public static readonly DependencyProperty UserListProperty = DependencyProperty.Register("UserList", typeof(ObservableCollection<UserItem>), typeof(MainPage), new PropertyMetadata((s, e) =>
{
cmbUserList.SelectedIndex = 0;
}));
You might be getting an index out of range because the data hasn't actually bound by the time you're specifying the index. Unfortunately there doesn't appear to be a data_loaded event or similar which would let you set the index when the data has been bound.
Could you use a data source that understands the concept of selected? Will ComboBox respect that attribute?
Use SelectedItem property of ComboBox for this goal.
Xaml:
<ComboBox ItemsSource="{Binding UserList}" SelectedItem="{Binding SelectedUser, Mode=TwoWay}" DisplayMemberPath="Name" />
View model:
public ObservableCollection<UserItem> UserList { get; set; }
private UserItem _selectedUser;
public UserItem SelectedUser
{
get { return _selectedUser; }
set { _selectedUser = value; }
}
For selecting first user in collection use command:
//NOTE: UserList must not be null here
SelectedUser = UserList.FirstOrDefault();
Related
I'm using the MVVM pattern with WPF and have run into a problem, which I can simplify to the following:
I have a CardType model.
public class CardType
{
public int Id { get; set; }
public string Name { get; set; }
}
And I have a viewmodel that consumes CardType.
public class ViewModel : INotifyPropertyChanged
{
private CardType selectedCardType;
public CardType SelectedCardType
{
get { return selectedCardType; }
set
{
selectedCardType = value;
OnPropertyChanged(nameof(SelectedCardType));
}
}
public IEnumerable<CardType> CardTypes { get; set; }
// ... and so on ...
}
My XAML has a ComboBox that bases its items on CardTypes and should preselect an item based on SelectedCardType.
<ComboBox ItemsSource="{Binding CardTypes}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedCardType}"/>
For reasons outside of my control, the SelectedCardType object will be a reference-unequal copy of the item in CardTypes. Therefore WPF fails to match the SelectedItem to an item in ItemsSource, and when I run the app, the ComboBox initially appears with no item selected.
I tried overriding the Equals() and GetHashCode() methods on CardType, but WPF still fails to match the items.
Given my peculiar constraints, how can I get ComboBox to select the correct item?
You might not be overriding Equals and GetHashCode properly. This should work for you. (However, just overriding Equals will work in your case but it's considered to be good practice to override GetHashCode too when you override Equals for a class)
public class CardType
{
public int Id { get; set; }
public string Name { get; set; }
public override bool Equals(object obj)
{
CardType cardType = obj as CardType;
return cardType.Id == Id && cardType.Name == Name;
}
public override int GetHashCode()
{
return Id.GetHashCode() & Name.GetHashCode();
}
}
You can use SelectedValue and SelectedValuePath:
<ComboBox ItemsSource="{Binding CardTypes}"
DisplayMemberPath="Name"
SelectedValue="{Binding ProductId, Mode=TwoWay}"
SelectedValuePath="Id"/>
Where ProductId is a int property with NotifyPropertyChanged.
Read a great explanation here:
Difference between SelectedItem, SelectedValue and SelectedValuePath
A workaround that you could do is to bind your SelectedItem to a string (instead of cardType), then create an object of type CardType using this string?
I have a wpf combobox bound to a property LogicalP of a class SInstance. The ItemSource for the combobox is a dictionary that contains items of type LogicalP.
If I set LogicalP in SInstance to an initial state, the combobox text field shows empty. If I select the pulldown all my dictionary values are there. When I change the selection LogicalP in SInstance gets updated correctly. If I change Sinstance in C# the appropriate combobox value doesn't reflect the updated LogicalP from the pulldown.
I've set the binding mode to twoway with no luck. Any thoughts?
My Xaml:
<UserControl.Resources>
<ObjectDataProvider x:Key="PList"
ObjectType="{x:Type src:MainWindow}"
MethodName="GetLogPList"/>
</UserControl.Resources>
<DataTemplate DataType="{x:Type src:SInstance}">
<Grid>
<ComboBox ItemsSource="{Binding Source={StaticResource PList}}"
DisplayMemberPath ="Value.Name"
SelectedValuePath="Value"
SelectedValue="{Binding Path=LogicalP,Mode=TwoWay}">
</ComboBox>
</Grid>
</DataTemplate>
My C#:
public Dictionary<string, LogicalPType> LogPList { get; private set; }
public Dictionary<string, LogicalPType> GetLogPList()
{
return LogPList;
}
public class LogicalPType
{
public string Name { get; set; }
public string C { get; set; }
public string M { get; set; }
}
public class SInstance : INotifyPropertyChanged
{
private LogicalPType _LogicalP;
public string Name { get; set; }
public LogicalPType LogicalP
{
get { return _LogicalP; }
set
{
if (_LogicalP != value)
{
_LogicalP = value;
NotifyPropertyChanged("LogicalP");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}
They are not looking at the same source.
You need to have SInstance supply both the LogPList and LogicalP.
_LogicalP is not connected to LogPList
If you want to different objects to compare to equal then you need to override Equals.
Here's my working solution. By moving the dictionary retrieval GetLogPList to the same class as that supplies the data (as suggested by Blam) I was able to get the binding to work both ways. I changed binding to a list rather than a dictionary to simplify the combobox
Here's the updated Xaml showing the new ItemsSource binding and removal of the SelectedValuePath:
<DataTemplate DataType="{x:Type src:SInstance}">
<Grid>
<ComboBox ItemsSource="{Binding GetLogPList}"
DisplayMemberPath ="Name"
SelectedValue="{Binding Path=LogicalP,Mode=TwoWay}">
</ComboBox>
</Grid>
</DataTemplate>
I then changed the dictionary LogPList to static so that it would be accessible to the class SInstance:
public static Dictionary<string, LogicalPType> LogPList { get; private set; }
Finally, I moved GetLogPList to the class SInstance as a property. Note again it's returning a list as opposed to a dictionary to make the Xaml a little simpler:
public class SInstance : INotifyPropertyChanged
{
public List<LogicalPType> GetLogPList
{
get { return MainWindow.LogPList.Values.ToList(); }
set { }
}
private LogicalPType _LogicalP;
public string Name { get; set; }
public LogicalPType LogicalP
{
get { return _LogicalP; }
set
{
if (_LogicalP != value)
{
_LogicalP = value;
NotifyPropertyChanged("LogicalP");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}
public class Emp
{
public string Id { get; set; }
}
I declared the class like this, but property i didnt set. I set the dependency property for textblock
public static readonly DependencyProperty LabelProperty
= DependencyProperty.Register("TextBlock", typeof(string), typeof(WindowsPhoneControl1), new PropertyMetadata(new PropertyChangedCallback(LabelChanged)));
public string List
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
This is perhaps not the answer, but there is something fundamentally wrong with your code.
You bind your ListBox's ItemSource property to a property Emp. Then in the Click handler you add objects of type Emp to the Items property of the ListBox. This won't work.
In order to make it work with binding, there has to be a property EmpList of some enumerable type, preferably ObservableCollection. The binding also needs to know the (model) object that defines this property. Therefore you must either set the ListBox's DataContext or you specify the Source of the binding.
When you add elements to the data-bound ListBox, you don't add them to the Items, but instead to the source property of the binding, EmpList here.
public class Model
{
private ICollection<Emp> empList = new ObservableCollection<Emp>();
public ICollection<Emp> EmpList { get { return empList; }}
}
Bind like this:
<ListBox ItemsSource="{Binding EmpList, Source={ an instance of Model }}" ... />
Or like below
<ListBox Name="listBox" ItemsSource="{Binding EmpList}" ... />
and set DataContext, perhaps in code:
listBox.DataContext = model; // where model is an instance of class Model
for (int i = 0; i < result.Length; i++)
{
Emp data = new Emp { Id = result[i] };
model.EmpList.Add(data);
}
Please can you tell me how can I bind combobox.
I had combobox which Itemsource is ObservableCollection<strings>. I wont to set Selected Combobox value to MainObject.SomeValue and vice versa.
Which is the easiest way
here's a little example.
I have two classes:
public class Person
{
private string _name = "Test2";
public String Name
{
get { return _name; }
set { _name = value; }
}
}
public class DataProvider
{
public ObservableCollection<String> Data { get; set; }
public DataProvider()
{
Data = new ObservableCollection<string>();
Data.Add("Test");
Data.Add("Test2");
Data.Add("Test3");
Data.Add("Test4");
}
}
The DataProvider provides the string data for the combo box and the Person is the object where you want to bind the name. This can be done as followed:
<Grid.Resources>
<myNamespace:DataProvider x:Key="DataProvider"/>
<myNamespace:Person x:Key="Person"/>
</Grid.Resources>
<ComboBox
Height="25"
DataContext="{StaticResource DataProvider}"
ItemsSource="{Binding Data}"
SelectedItem="{Binding Name, Source={StaticResource Person}, Mode=TwoWay}"/>
This is just a quick example. Have a look at SelectedItem, SelectedValue, SelectedValuePath if you don't want to use string as input data...
Is this what you needed?
BR,
TJ
Tried may approches to displaying a "no data" if there are no items in listbox. Since I'm on wp7 and using silverlight I can't use DataTriggers, so I've created a control to have it behave consistently across the whole app. BUT I if you set the breakpoint for the set method - it's not being called at all!
The control class
public class EmptyListBox : ListBox
{
public new IEnumerable ItemsSource
{
get
{
return base.ItemsSource;
}
set
{
// never here
base.ItemsSource = value;
ItemsSourceChanged();
}
}
protected virtual void ItemsSourceChanged()
{
bool noItems = Items.Count == 0;
if (noItems)
{
if (Parent is System.Windows.Controls.Panel)
{
var p = Parent as Panel;
TextBlock noData = new TextBlock();
noData.Text = "No data";
noData.HorizontalAlignment = HorizontalAlignment;
noData.Width = Width;
noData.Height = Height;
noData.Margin = Margin;
p.Children.Add(noData);
Visibility = System.Windows.Visibility.Collapsed;
}
}
}
}
This is xaml
<my:EmptyListBox ItemsSource="{Binding Path=MyData}" Name="myListBox">
<my:EmptyListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=name}" />
</DataTemplate>
</my:EmptyListBox.ItemTemplate>
</my:EmptyListBox>
Codebehind:
ClientModel ClientInfo { get; set; }
public ClientView()
{
ClientInfo = new ClientModel();
ClientInfo.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(DataReady);
DataContext = ClientInfo
}
ClientModel class:
public class ClientModel : INotifyPropertyChanged
{
MyData _myData;
public MyData MyData
{
get
{
return _myData;
}
set
{
_myData = value;
NotifyPropertyChanged("MyData");
}
}
public void GetClient(int id)
{
// fetch the network for data
}
}
LINK TO SOLUTION .ZIP THAT SHOWS THE PROBLEM
http://rapidshare.com/files/455900509/WindowsPhoneDataBoundApplication1.zip
Your new ItemSource should be a DependencyProperty.
Anything that is working with Bindings have to be a DependencyProperty.
Simply make it a DependencyProperty.
I think the solution I'd go for is something like this:
Define a new visual state group ItemsStates and two visual states: NoItems and HasItems.
In the ControlTemplate for your custom listbox, add the visual tree for your "no data" state.
In the NoItems state, set the Visibility of your "no data" elements to Visible and set the Visibility of the default ItemsPresenter to Collapsed.
In the HasItems state, swap the Visibility of these elements.
In an OnApplyTemplate override switch to the Empty state by default: VisualStateManager.GoToState(this, "Empty", true);
In an OnItemsChanged override, check whether the items source is empty and use VisualStateManager to switch between these states accordingly.
That should work :)
Create ItemsSource as a DependencyProperty.
Example:
public IEnumerable ItemsSource
{
get { return (IEnumerable)base.GetValue(ItemsSourceProperty); }
set { base.SetValue(ItemsSourceProperty, value); }
}
public static DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
"ItemsSource",
typeof(IEnumerable),
typeof(EmptyListBox),
new PropertyMetadata(null));
try to implement the INotifyPropertyChanged interface and use for ItemsSource an ObservableCollection. In the Setter of your Property just call the OnPropertyChanged method.
Maybe this will help.
Try adding Mode=TwoWay to the ItemsSource binding:
<my:EmptyListBox ItemsSource="{Binding Path=MyData, Mode=TwoWay}" Name="myListBox">
<my:EmptyListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=name}" />
</DataTemplate>
</my:EmptyListBox.ItemTemplate>
</my:EmptyListBox>