I'm trying to construct a ComboBox that will display a selected item. I can get the combo box to display the options easily enough, but getting the selected item to display is not working. The selected item refers to a value being pulled from a database.
I know that the set method is working because I can successfully store the user's choice. However, I cannot seem to get the ComboBox to populate the selected item when I grab it from the database.
I've been frustrated over this for a few weeks now, and I feel like the mistake is simple.
Scenario:
I have an Animal Model
public class AnimalModel
{
public string AnimalName { get; set; }
...
}
I have an AnimalViewModel for each Animal:
public class AnimalViewModel: BindableBase
{
private AnimalViewModel _animal;
public AnimalViewModel(AnimalModel animal)
{
_animal = animal;
}
public string AnimalName
{
get { return _animal.Name; }
set
{
if (value != this._animal.Name)
{
this._animal.Name = value;
OnPropertyChanged("AnimalName");
}
}
}
...
}
I have an ObservableCollection of AnimalViewModel objects:
public class TemplateViewModel : BindableBase
{
private ObservableCollection<AnimalViewModel> _animals;
public TemplateViewModel(...)
{
_animal = methodReturnsAnObservableCollectionOfAnimalViewModels();
}
public ObservableCollection<AnimalViewModel> Animals
{
get { return _animals; }
set
{
if (value != this._animals)
{
this._animals = value;
OnPropertyChanged("Animals");
}
}
}
}
With this in place, I can easily display a list of AnimalNames in the ComboBox:
<ComboBox ItemsSource="{Binding Animals}"
DisplayMemberPath="AnimalName"
IsEditable="True"
IsReadOnly="True"
Text="--- Select Animal ---"/>
I now bind to a SelectedItem
public class TemplateViewModel
{
...
private AnimalViewModel _selectedAnimal;
public TemplateViewModel(MyObject, ...)
{
...
_selectedAnimal = new AnimalViewModel(new AnimalModel() { AnimalName = MyObject.AnimalName });
}
...
public AnimalViewModel SelectedAnimal
{
get { return _selectedAnimal; }
set
{
if (value != _selectedAnimal)
{
_selectedAnimal = value;
AnimalName = value.AnimalName;
OnPropertyChanged("SelectedAnimal");
}
}
}
}
So now, I have:
<ComboBox ItemsSource="{Binding Animals}"
DisplayMemberPath="AnimalName"
SelectedItem={Binding SelectedAnimal}
SelectedValuePath="AnimalName"
IsEditable="True" IsReadOnly="True"
Text="--- Select Animal ---"/>
Unfortunately, this does not populate the ComboBox with an Animal. It just pulls up the default choice Select Animal with the options populated. It does set the item correctly.
Any help would be appreciated.
You need to access the actual reference to the animal in question.
In the codebehind you create an animal which looks like the animal in the ObservableCollection, but its not the animal by reference.
Change (after Animals is loaded)
_selectedAnimal = new AnimalViewModel(...);
to (only use the Property accessor, not the backing store variable; so the change event fires, btw)
SelectedAnimal = Animals[0];
Test app (formatting like grid row/column removed)
<Label>SelectedItem</Label>
<TextBlock Text="{Binding SelectedItem, ElementName=cbMain}"/>
<Label >SelectedValue</Label>
<TextBlock Text="{Binding SelectedValue, ElementName=cbMain}"/>
<Button Click="ChangeSelectedValue">Set Selected Value</Button>
<ComboBox Name="cbMain"
ItemsSource="{Binding Ships}"
SelectedItem="{Binding Ships[0]}"
SelectedValuePath="Name"
Text="Select a Ship"/>
Result on Load of screen:
I think this is a naming discrepancy between SelectedValuePath="AnimalName" and your property.
public string Animal //<----Should Be AnimalName!
{
get { return _animal.Name; }
set
{
if (value != this._animal.Name)
{
this._animal.Name = value;
OnPropertyChanged("AnimalName");
}
}
}
change your property to AnimalName and you're good to go.
Related
I have a combobox, which draws it's items from an ObservableCollection of a custom type using Bindings. I've set the DisplayMemberPath so it displays the correct string and stuff. Now I'm fiddling with the SelectedItem/SelectedValue. It needs to be dependant on the selected item of a ListBox, which is bound to a different ObservableCollection of another custom type, but which has a property of the same type of the ComboBox list.
How can I bind this using MVVM? Is it even possible?
I've got my code here:
MainWindowViewModel.cs
private ObservableCollection<Plugin<IPlugin>> erpPlugins;
public ObservableCollection<Plugin<IPlugin>> ERPPlugins
{
get
{
return erpPlugins;
}
set
{
erpPlugins = value;
OnProprtyChanged();
}
}
private ObservableCollection<Plugin<IPlugin>> shopPlugins;
public ObservableCollection<Plugin<IPlugin>> ShopPlugins
{
get
{
return shopPlugins;
}
set
{
shopPlugins = value;
OnProprtyChanged();
}
}
private ObservableCollection<Connection> connections;
public ObservableCollection<Connection> Connections
{
get {
return connections;
}
set
{
connections = value;
}
}
public MainWindowViewModel()
{
instance = this;
ERPPlugins = new ObservableCollection<Plugin<IPlugin>>(GenericPluginLoader<IPlugin>.LoadPlugins("plugins").Where(x => x.PluginInstance.Info.Type == PluginType.ERP));
ShopPlugins = new ObservableCollection<Plugin<IPlugin>>(GenericPluginLoader<IPlugin>.LoadPlugins("plugins").Where(x => x.PluginInstance.Info.Type == PluginType.SHOP));
Connections = new ObservableCollection<Connection>
{
new Connection("test") { ERP = ERPPlugins[0].PluginInstance, Shop = ShopPlugins[0].PluginInstance } // Debug
};
}
Connection.cs
public class Connection
{
public string ConnectionName { get; set; }
public IPlugin ERP { get; set; }
public IPlugin Shop { get; set; }
public Connection(string connName)
{
ConnectionName = connName;
}
}
And the XAML snippet of my ComboBox:
<ComboBox
Margin="10,77,232,0"
VerticalAlignment="Top"
x:Name="cmbERP"
ItemsSource="{Binding ERPPlugins}"
SelectedItem="{Binding ElementName=lbVerbindungen, Path=SelectedItem.ERP}"
DisplayMemberPath="PluginInstance.Info.Name"
>
Alright, I solved it by changing the IPlugin type in the Connection to Plugin. Why I used IPlugin there in the first place is beyond my knowledge. But like this, I have the same type of Plugin everywhere.
Thanks for your help, appreciate it
i just started learning WPF as i am moving on from WinForm. At the moment i am having difficulties displaying bind data from class to tree view.
My tree view works perfectly if i use .Items.Add() method but when it comes to binding class data to TreeView this is what i see:
Here is the c# code:
public MainWindow()
{
InitializeComponent();
Search sc = new Search();
sc.query(null, "");
this.DataContext = sc;
}
Here is the xaml
<TreeView Width="400" Height="500" Name="TreeViewB" ItemsSource="{Binding getTreeResults}" Style="{StaticResource myTreeView}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Network}">
<TextBlock Text="{Binding getNetwork}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
Edited - 2 class added
Here is my class A
class Social_Searcher
{
List<Social_Network> networks = new List<Social_Network>();
public List<Social_Network> getTreeResults { get { return networks; } }
}
Here is my class B
class Social_Network
{
private string network_name;
private List<Keypair> data;
public Social_Network()
{
data = new List<Keypair>();
}
public struct Keypair
{
public void add(string _name, string _value)
{
name = _name;
value = _value;
}
public string name, value;
}
public string Network
{
get { return network_name; }
set { network_name = value; }
}
public void add(string name, string value)
{
if (name == "network")
{
network_name = value;
}
Keypair kp = new Keypair();
kp.add(name, value);
data.Add(kp);
}
public string getNetwork()
{
return network_name;
}
public List<Keypair> getData()
{
return data;
}
public string findKey_value(string key)
{
foreach (Keypair kp in data)
{
if (kp.name == key) return kp.value.ToString();
}
return "null";
}
}
You don't give much code, but getTreeResults and getNetwork look like methods, and your TextBlock will not know how to present them (normally, it would use the results of ToString(), but I don't know if that will work with a method.
If you want those methods, you can try it this way:
public string TreeResults { get { return sc.getTreeResuls(); }}
and then
<TreeView ... ItemsSource={Binding TreeResults} ... > ...
The same goes for getNetwork. I.e., you wrap each method in a public property.
If you don't want to do that, or can't, you can use an IValueConverter
There is clearly something going on in your UI, but it's hard to tell what exactly.
You will likely find a debugging tool such as Snoop useful, as it will allow you to click on items in your UI and see how they exist in the logical tree. You can modify their properties while the program is running to experiment and learn what you need to change in your source code.
I ran into this issue when converting a Windows Forms application to WPF. I know it sounds ridiculous, but make sure that your value is stored in the TreeViewItem's "Header" property, NOT the "Name" property. Once I did this, my list populated as expected.
i hope you can help me, because I've an very special question.
I have an View with an Datagrid. The DataContext I generate Dynamically. This is done by resolving some Interface (lets say IMyDataContext) over MEF.
This Interface has some Properties. The interesting Property for this problem is
ICollection Data {get;}
This Property is Implemented in the Backend by an ObservableCollection of an type which is known in the class which implements the Interface.
Now the Problem is, that the DataGrid don't Generate the columns. Even if I Listen to the IPropertyChanged Event of the Specific Class and then Update the Layout. Nothing changes.
So my Question is, how can I resolve this Issue?
Or is there an better Solution for my Problem?
I also tried to make the Interface Generic so that it looks like the following:
public Interface IMyDataContext<CollectionType>
{
ObservableCollection<CollectionType> Data {get;}
}
The Implementing Class Exports then with [Export(typeof(IMyDataContext<>)] or [Export(typeof(IMyDataContext<object>)]
and the class which registers the View with the DataContext via PRISM resolves them with [ImportMany(typeof(IMyDataContext<object>)] but this don't work with an ObservableCollection.
So this is why I declared the Collection as ICollection in the Interface.
Can someone explain me why it's not generating the Columns?
The Code for Binding in the View:
<DataGrid
ItemsSource="{Binding Data, UpdateSourceTrigger= PropertyChanged}"
CanUserAddRows="False"
CanUserDeleteRows="False"
AutoGenerateColumns="True"
ClipboardCopyMode="IncludeHeader" />
// Edit:
I've checked that the Data are there. And yes they are available in the View. Other ViewElements which binds also on this Data Field works perfectly. And they are directly below the Datagrid.
Example (Pseudo Code):
<DataGrid ItemsSource="{Binding Data}" /> <!-- Dont work -->
<Label Content="No Data Available" IsVisible="{Binding Data, Convert={StaticResource CollectionIsEmptyToVisibleConverter}" /> <!-- Work -->
For Information:
I fill the collection at once in an other Thread. But to be Thread Safe I Use the Dispatcher like this:
var items = new List<MyItemType>(); // MyItemType is an Class. this class I want to Display in the Datagrid
// ... fill items
Application.Current.Dispatcher.Invoke(new Action(() => this.Data = new ObservableCollection<MyItemType>(items)));
// Edit 2:
Ok, here is a code Example:
The Interface which i Mean:
public interface IDataContextInterface: INotifyPropertyChanged
{
ICollection Data { get; }
// ... Some other Properties
}
An Example Implementation of the Interface (I have multiple Implementations):
[Export(typeof(IDataContextInterface))]
public class ExampleDataContext1 : IDataContextInterface
{
private ObservableCollection<MyExampleDatagridEntry> _data;
public ObservableCollection<object> Data
{
get
{
return this._data;
}
set
{
if (this._data == value)
{
return;
}
if (value == null)
{
this._data = null;
RaisePropertyChanged("Data");
return;
}
var values = value.OfType<MyExampleDatagridEntry>();
if (values != null)
{
this._data = new ObservableCollection<MyExampleDatagridEntry>(values);
}
else
{
this._data = null;
}
RaisePropertyChanged("Data");
}
}
// Will be called in an Background Thread
public void Update()
{
var data = new ObservableCollection<MyExampleDatagridEntry>();
// .. fill the data
Application.Current.Dispatcher.Invoke(new Action(() =>
this._data =data));
}
}
The Data for this Example Model looks like:
public class MyExampleDatagridEntry : INotifyPropertyChanged
{
private _exampleString1;
public string ExampleString1
{
get
{
return this._exampleString1;
}
set
{
if (this._exampleString1 != value)
{
this._exampleString1 = value;
RaisePropertyChanged("ExampleString1");
}
}
}
private _exampleString2;
public string ExampleString2
{
get
{
return this._exampleString2;
}
set
{
if (this._exampleString2 != value)
{
this._exampleString2 = value;
RaisePropertyChanged("ExampleString2");
}
}
}
// .. Some other Properties
}
The Module which import all of them and register an new View to the Region (I don't know if there is a better way):
[ModuleExport(typeof(MyViewModule))]
public class MyViewModule : IModule
{
private ObservableCollection<IDataContextInterface> _dataContextModels;
private IRegionManager _regionManager;
[ImportingConstructor]
public MyViewModule(IRegionManager regionManager)
{
this._regionManager = regionManager;
this.DataContextModels.CollectionChanged += (s, e) => this.UpdateViews();
}
[ImportMany(typeof(IDataContextInterface))]
public ObservableCollection<IDataContextInterface> DataContextModels
{
get
{
return this._dataContextModels ?? (this._dataContextModels = new ObservableCollection<IDataContextInterface>());
}
set
{
this._dataContextModels = value;
this.UpdateViews();
}
}
private void UpdateViews()
{
foreach (var dataContext in this.DataContextModels)
{
// Short version of it. Normally i check if its registered
var view = new MyViewForDataContext{ dataContext = dataContext };
this._regionManager.Regions["MyRegion"].Add(view);
}
}
}
And finally the View looks like this (Shorten to have it simpler):
<UserControl x:Class="MyViewForDataContext">
<!-- Some Other stuff here -->
<Grid>
<!-- This Datagrid don't generate the Columns -->
<DataGrid ItemsSource="{Binding Data}" AutoGenerateColumns="True" />
<!-- This one works -->
<Label Content="No Data Available" Visibility={Binding Data, Converter={StaticResource EmptyListToVisiblityConverter}} />
</Grid>
</UserControl>
// Edit 3 (Solution):
Ok I finally solved it. It was an Problem by my behavior which I bound to the Datagrid. In this Behavior I tried to refresh the Datagrid, which worked fine. But not if the ItemsSource is null of the Datagrid. For this reason i had to insert an null check of the ItemsSource.
I am trying to use the following code example from the Infragistics site and I'd like edits in the XamDataCards to be reflected in the XamDataGrid. However, my DataSource for the XamDataGrid is an ObservableCollection<Companies> in my ViewModel. How can I also bind to the card and relay updates back to my Companies object in the ViewModel?
<igDP:XamDataGrid x:Name="dgCompanies" Theme="IGTheme" DataSource="{Binding Companies}" SelectedDataItemsScope="RecordsOnly">
<igDP:XamDataGrid.FieldSettings>
<igDP:FieldSettings CellClickAction="SelectCell" AllowEdit="True"/>
</igDP:XamDataGrid.FieldSettings>
</igDP:XamDataGrid>
<igDP:XamDataCards x:Name="XamDataCards1"
Grid.Row="1"
DataSource="{Binding Path=SelectedDataItems, ElementName=dgCompanies}"
Theme="IGTheme">
Edit: Added ViewModel
public class CompanyMgmtViewModel : ViewModelBase
{
private ObservableCollection<Object> _Companies = null;
public ObservableCollection<Object> Companies
{
get { return _Companies; }
set
{
if (_Companies != value)
{
_Companies = value;
RaisePropertyChanged(GetPropertyName(() => Companies));
}
}
}
public CompanyMgmtViewModel()
{
this.LoadData();
}
public void LoadData()
{
ObservableCollection<Object> records = new ObservableCollection<Object>();
var results = from res in AODB.Context.TCompanies
select res;
foreach (var item in results)
if (item != null) records.Add(item);
Companies = records;
}
}
The Model/Context code is just EF Database First generated.
You would need to bind your XamDataGrid's SelectedDataItems property to a property of type object[] ie. SelectedCompanies in your ViewModel and bind to that for your XamDataCards' datasource.
The accepted answer in this thread has a sample that shows how to do this, albeit with a ListBox instead of XamDataCards:
http://www.infragistics.com/community/forums/t/89122.aspx
Just replace that ListBox with your XamDataCards control, it works and updates the XamDataGrid. The ViewModel in the example is contained in the MainWindow code-behind, so it is MVVM like you want.
more info:
http://help.infragistics.com/Help/Doc/WPF/2014.1/CLR4.0/html/xamDataGrid_Selected_Data_Items.html
IG's SelectedDataItems is an object[] :
http://help.infragistics.com/Help/Doc/WPF/2014.1/CLR4.0/html/InfragisticsWPF4.DataPresenter.v14.1~Infragistics.Windows.DataPresenter.DataPresenterBase~SelectedDataItems.html
I couldn't have gotten to this answer without Theodosius' and Ganesh's input - so thanks to them, they both had partial answers.
I first tried to bind the SelectedDataItems of the XamDataGrid to the XamDataCards by way of a property on the ViewModel as Theodosius suggested, but that wasn't enough. Thanks to Ganesh, I implemented INotifyPropertyChanged on my model objects, by inheriting from ObservableObject in MVVMLight (how did I not know the Model needed this?).
Below are the relevant pieces of code to make it work.
I also implemented PropertyChanged.Fody as documented here; that's where the TypedViewModelBase<T> and removal of RaisePropertyChanged() comes from.
I'm also creating my Model objects by using a LINQ/Automapper .Project().To<T>() call which can be found here.
Model
public class Company : ObservableObject
{
public Company() { }
public int id { get; set; }
public string strName { get; set; }
public string strDomicileCode { get; set; }
}
ViewModel
public class CompanyMgmtViewModel : TypedViewModelBase<Company>
{
private ObservableCollection<Object> _Companies = null;
private Object[] _selectedCompany = null;
public Object[] Company
{
get { return _selectedCompany; }
set
{
if (_Company != value)
{
_selectedCompany = value;
}
}
}
public ObservableCollection<Object> Companies
{
get { return _Companies; }
set
{
if (_Companies != value)
{
_Companies = value;
}
}
}
public CompanyMgmtViewModel()
{
this.LoadData();
}
public void LoadData()
{
ObservableCollection<Object> records = new ObservableCollection<Object>();
var results = AODB.Context.TCompanies.Project().To<Company>();
foreach (var item in results)
if (item != null) records.Add(item);
Companies = records;
}
}
View
<igDP:XamDataGrid x:Name="dgCompanies"
Theme="IGTheme"
DataSource="{Binding Companies, Mode=OneWay}"
SelectedDataItemsScope="RecordsOnly"
SelectedDataItems="{Binding Company}">
...
<igDP:XamDataCards x:Name="XamDataCards1"
Grid.Row="1"
DataSource="{Binding ElementName=dgCompanies, Path=SelectedDataItems}"
Theme="IGTheme">
I got a combo box in wpf form
I set the ItemSource to the collection of Dictionary of (Pet Type) and just display the Value and hid the Key
public void BindComboBoxes()
{
this.cboTypes.ItemsSource = new BindingSource(CommonMgr.GetPetTypesDropDown(false), null);
this.cboTypes.DisplayMemberPath = "Value";
this.cboTypes.SelectedValuePath = "Key";
}
Then whenever I type to encode a new Breed Object, and type a text in the cboTypes of something that doesn't exist in its items(not in the db), my program will ask if the end user wants to add that new PetType in the db, if yes, then it will do so.
Then i update the cboTypes using the BindComboBoxes method again, set the cboTypes.Text into the new item and assign the Key to the designated field, but the problem is, it says, it was null. it worked fine in the windows form though. Here's my code:
public Breed GetPageEntity()
{
Breed setEntity = new Breed();
bool doesExist = false;
setEntity.Id = DefaultValue.GetInt(this.txtId.Text);
setEntity.BreedName = DefaultValue.GetString(this.txtName.Text);
try
{
setEntity.PetTypeId = DefaultValue.GetInt(this.cboTypes.SelectedValue.ToString());
}
catch (Exception)
{
var addAnother = MessageBox.Show(String.Format("{0}: This type is not in the database. \nAdd {0} to the database?",
this.cboTypes.Text), "Pet Type Cannot Be Found", MessageBoxButtons.OKCancel);
if (addAnother == System.Windows.Forms.DialogResult.OK)
{
petTypeMgr.Entity = this.PetTypeAdder(cboTypes.Text);
string temp = this.cboTypes.Text;
petTypeMgr.Insert((petTypeMgr.Entity), fUser.Entity.Id, ref doesExist);
//cboTypes.ItemsSource = null;
//cboTypes.Items.Clear();
BindComboBoxes();
cboTypes.Text = temp;
//SelectedValue became null
setEntity.PetTypeId = DefaultValue.GetInt(this.cboTypes.SelectedValue);
}
}
setEntity.Description = DefaultValue.GetString(this.txtDescription.Text);
setEntity.SortOrder = DefaultValue.GetInt(txtSortOrder.Text);
setEntity.StatusId = true;
return setEntity;
}
You'll find it much easier if you data bind to properties in the code behind:
// Implement INotifyPropertyChanged interface properly here
private Dictionary<string, Pet> yourProperty = new Dictionary<string, Pet>();
public Dictionary<string, Pet> YourProperty
{
get { return yourProperty; }
set
{
yourProperty = value;
NotifyPropertyChanged("YourProperty");
}
}
private KeyValuePair<string, int> yourSelectedProperty;
public KeyValuePair<string, int> YourSelectedProperty
{
get { return yourSelectedProperty; }
set
{
yourSelectedProperty = value;
NotifyPropertyChanged("YourSelectedProperty");
}
}
Then in the XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ComboBox ItemsSource="{Binding YourProperty}" DisplayMemberPath="Value"
SelectedValuePath="Key" SelectedItem="{Binding YourSelectedProperty}" />
<TextBlock Grid.Column="1" Text="{Binding YourSelectedProperty.Key}" />
</Grid>
You only need to set your ItemsSource once like this. Once it is data bound to a collection property, you can just make changes to the collection and they will update in the UI automatically. So, assuming that your GetPetTypesDropDown method returns the correct type, you should be able to update the ComboBox items like this:
YourProperty = CommonMgr.GetPetTypesDropDown(false);
Alternatively, you could equally do something like this to update it:
YourProperty = new Dictionary<string, int>();
foreach (YourDataType dataType in CommonMgr.GetPetTypesDropDown(false))
{
YourProperty.Add(dataType.Key, dataType.Value);
}
Why don't you just bind Breed to the Combobox?
In the Breed class override the ToString() method so that the box shows what you want it to.
class Breeds
{
//Variables
public void override ToString()
{
return Breedname;
}
}
Set the combobox
List<Breeds> breedlist = new List<Breeds>();
this.cboTypes.ItemsSource = breedlist;
Read the combobox
if(cboTypes.SelectedItem != null)
{
Breeds breed = (Breeds)cboTypes.SelectedItem;
//Do stuff
}
else
{
//Create new breed
}