I have an editable combobox that I bind the name property from a list of objects to (QBD.Name). What I can't figure out is how to allow editing of those names - I keep getting an object reference error when I try to edit.
I believe I need to implement INotifyPropertyChanged, but I'm not entirely sure how that works.
Here's the binding code:
<ComboBox Name="cmbBxQBDNames" Text="Please Select a QBD" ItemsSource="{Binding Path=QBDs, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" DisplayMemberPath="QBD.Name" SelectedValuePath="QBD.Name" IsEditable="True" VerticalAlignment="Center" HorizontalAlignment="Stretch" Width="auto" MinWidth="25" Margin="45,0,0,0" Foreground="Black"></ComboBox>
Here's the objects I'm binding to:
Public Class QBDs
Private QBDsLocal As New ObservableCollection(Of QBD)
Public Property QBDs As ObservableCollection(Of QBD)
Get
Return QBDsLocal
End Get
Set(value As ObservableCollection(Of QBD))
QBDsLocal = value
End Set
End Property
End Class
Public Class QBD
Private NameLocal As String
Public Property Name As String
Get
Return NameLocal
End Get
Set(value As String)
NameLocal = value
End Set
End Property
End Class
Also, when I select an object from the combobox, how can I have it's name displayed in the combobox? Currently, it remains blank.
i think your problem is with DisplayMemberPath.
try with DisplayMemberPath = "Name"
let me know if it fails.
I couldn't get why this fails,Please see the Code i've written to test your problem.
<ComboBox ItemsSource="{Binding MyCollection}" DisplayMemberPath="FName" SelectedValuePath="SName" Height="40" IsEditable="True" />
//My DataContext Goes here
public class Model
{
private string sName;
public string SName
{
get { return sName; }
set { sName = value; }
}
private string fName;
public string FName
{
get { return fName; }
set { fName = value; }
}
}
public class ViewModel
{
private ObservableCollection<Model> myColl;
public ObservableCollection<Model> MyCollection
{
get { return myColl; }
set { myColl = value; }
}
public ViewModel()
{
MyCollection = new ObservableCollection<Model>();
MyCollection.Add(new Model { FName = "Tony", SName = "Strark" });
MyCollection.Add(new Model { FName = "Bruce", SName = "Wayne" });
MyCollection.Add(new Model { FName = "Miranda", SName = "Frost" });
}
}
//and I ve set ViewModel as DataContext,
This works just fine for me please check it once,and please forgive me for not giving the code in VB .
Regards,
Kumar
Related
I have a simple dialog that contains edit boxes such as this:
<TextBox Text="{Binding Path=EmailSettings.SmtpServer, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}" />
The dialog uses a Model as its data context (to simplify the model example INotifyPropertyChanged has not been shown nor is the code that creates the model and
sets the dialog data context to the model instance):
class EmailSettingsModel : IDataErrorInfo
{
public EmailSettingsModel ()
{
EmailSettings = new EmailSettings();
}
public EmailSettings EmailSettings
{ get; set; }
string _error;
public string Error
{
get { return _error; }
set { _error = value; }
}
public string this[string propertyName]
{
get
{
string errorMessage = null;
if ( string.Compare( propertyName, "EmailSettings.SmtpServer" ) == 0 )
{
if ( !string.IsNullOrWhiteSpace( EmailSettings.SmtpServer ) )
errorMessage = "SMTP server is not valid";
}
Error = errorMessage;
}
}
}
The model contains a property that is a simple POCO class that has several properties on it.
class EmailSettings
{
public string SmtpServer
{ get; set; }
}
I could not get the IDataErrorInfo indexer to fire and spent hours looking. When I changed the binding on the text box to use a simple property:
<TextBox Text="{Binding Path=SmtpServer, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}" />
on the Model as below the IDataErrorInfo indexer fired.
class EmailSettingsModel
{
public string SmtpServer
{ get; set; }
}
Was IDataErrorInfo not called because I used a compound property for the binding statement. I have used complex properties like this for normal data binding and they work but for this example IDataErrorInfo was not called.
IDataErrorInfo fires only at the level where implemented
For example if you have Binding Path looking like this "viewModel.property1.property2.property3" you will need to implement IDataErrorInfo inside the class of viewModel and inside the class of property1 and inside the class of property2. Property3 is a string.
So in order to make it work for you just implement IDataErrorInfo anywhere else.
I have a simple listbox in a template file as follows:
<local:ProcessVisualization x:Key="ProcessVisualization"/>
<ListBox Grid.Column="1"
Grid.Row="1"
ItemsSource="{Binding Source={StaticResource ResourceKey=ProcessVisualization}, Path=Instance.TestListItems}"
SelectedItem="{Binding Source={StaticResource ResourceKey=ProcessVisualization}, Path=Instance.SelectedTestListItem, Mode=TwoWay}">
</ListBox>
Then in my ProcessVisualization class I have the following:
private ObservableCollection<string> _testListItems;
private string _selectedTestListItem;
private static readonly ProcessVisualization _processVisualization = new ProcessVisualization();
public ObservableCollection<string> TestListItems
{
get { return _testListItems; }
set
{
_testListItems = value;
NotifyPropertyChanged("TestListItems");
}
}
public string SelectedTestListItem
{
get { return _selectedTestListItem; }
set
{
_selectedTestListItem = value;
NotifyPropertyChanged("SelectedTestListItem");
}
}
public static ProcessVisualization Instance
{
get { return _processVisualization; }
}
When I run methods that assign lists of strings to TestListItems, they show up properly in my listbox, and I can set SelectedTestListItem from code without issue. But if a user tries to pick from the listbox, it doesn't seem to get back to updating my property on the ProcessVisualization class. Anyone know what I did wrong?
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();
I have the following scenario:
I have one window say MainWindow where I am displaying the list of activities as per the specific user from the database. There is a one button present on the window. By clicking on that button a new window is getting opened having all the list of activities from the master table. Now I want add a chechbox on the second window against each item dynamically so that user can select/deselect the activities. Those selected/deselected values should save in the database and Parent/MainWindow should refreshed after clicking on the done button and changes should reflect in the MianWindow. But I am not getting how to dynamically creating the checkboxes against each list item and binding with the xaml and select/deselect the checkbox.
Kindly suggest with samples or examples.
Thanks
You can customize your listviewitem using the ListView's ItemTemplate. Add a checkbox
and a textblock to a panel which would constitute your datatemplate.
Update
The Model:
public class Activity
{
public Activity(int id, string name)
{
ID = id;
Name = name;
}
public int ID { get; set; }
public string Name { get; set; }
}
The ViewModel for ListViewItem in Second Window:
public class ActivityViewModel
{
Activity _model;
public ActivityViewModel(Activity model, bool isSelected)
{
_model = model;
IsSelected = isSelected;
}
public string Name { get { return _model.Name; } }
/* Since the view has a checkbox and it requires a bool value for binding
we create this property */
public Nullable<bool> IsSelected { get; set; }
}
The DataAccess
public class DaoDailyActivities
{
string activityName = "";
bool IsSelected;
SqlConnection con = new SqlConnection("server=172.16.32.68;database=ParentalHealth;uid=sa;pwd=Emids123");
public IEnumerable<Activity> GetActivities()
{
SqlCommand cmd = new SqlCommand("SP_GetActivities", con);
cmd.CommandType = CommandType.StoredProcedure;
con.Open(); /* It is safe to open connections in a try block */
SqlDataReader readerActivities = cmd.ExecuteReader();
while (readerActivities.Read())
{
yield new Activity(readerActivities["ActivityID"].ToString(), readerActivities["ActivityName"].ToString());
}
}
}
The ViewModel for SecondWindow:
public class SecondWindowViewModel : ViewModelBase
{
DaoDailyActivities _rep = new DaoDailyActivities();
public ObservableCollection<ActivityViewModel> AllActivities { get; set; }
public SecondWindowViewModel()
{
LoadAllActivities();
}
LoadAllActivities()
{
foreach(Activity activity in _rep.GetActivities())
{
AllActivities.Add(new ActivityViewModel(activity, (activity.ID % 2 == 0)));
}
}
}
The XAML:
<ListView ItemsSource="{Binding AllActivities}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
<CheckBox IsChecked="{Binding Path=IsSelected}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
I have a gridview were I define some columns, like this...
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyProp}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
I bind my gridview to a collection and implemts INotifyPropertyChanged in the property MyProp. This works well and any changes of MyProp are reflected to the gridview.
If I add another column that is bound to the object itself I dont get any notifications/updates. My code...
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource myConverter}}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
I think I need something like INotifyPropertyChanged for the object but I have no idea how to do this. Any suggestions?
Yes, the actual instance itself never changes - only its properties.
Presumably your converter relies on a bunch of properties from the object you've bound to? If so, you could use a MultiBinding and change your converter to an IMultiValueConverter. Then you can bind to all the dependent properties that might cause the TextBlock to update.
Make the object impletment the interface INotifyPropertyChanged
Here is an example from MSDN
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Guid idValue = Guid.NewGuid();
private string customerName = String.Empty;
private string companyNameValue = String.Empty;
private string phoneNumberValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
// The constructor is private to enforce the factory pattern.
private DemoCustomer()
{
customerName = "no data";
companyNameValue = "no data";
phoneNumberValue = "no data";
}
// This is the public factory method.
public static DemoCustomer CreateNewCustomer()
{
return new DemoCustomer();
}
// This property represents an ID, suitable
// for use as a primary key in a database.
public Guid ID
{
get
{
return this.idValue;
}
}
public string CompanyName
{
get {return this.companyNameValue;}
set
{
if (value != this.companyNameValue)
{
this.companyNameValue = value;
NotifyPropertyChanged("CompanyName");
}
}
}
public string PhoneNumber
{
get { return this.phoneNumberValue; }
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
NotifyPropertyChanged("PhoneNumber");
}
}
}
}