I have a requirement where I need to bind a list of observable collections to the item source of dynamically created ComboBoxes.
The problem is we bind through xaml conventionally in ItemsSource property but now the control rows in the grid are being added dynamically so each ComboBox in a row reference to the same collection whereas I need to bind it to a separate collection in observable collection list each time a row in the grid is added.
This what I have tried so far, any guidance would be appreciated. Thanks.
public virtual List<ObservableCollection<ComboBoxEntity>> ListRewardRule { get; set; }
Xaml :
<itimControls:ComboBox Name="cboReward"
IsMandatory="True"
itimComponents:ComponentManager.ComponentId="TXT_GROUP_RULE"
MaxWidth="400"
MinWidth="150"
ItemsSource="{Binding ListRewardRule, ElementName=RDefinitionScreen}"
DisplayMemberPath="Name"
SelectedValuePath="Code"
Loaded="cboReward_Loaded"
SelectedValue="{Binding RewardRuleId, Mode=TwoWay}"
SelectionChanged="cboReward_SelectionChanged">
</itimControls:ComboBox>
.CS :
private void cboReward_Loaded(object sender, RoutedEventArgs e)
{
Itim.Framework.Silverlight.UI.Controls.ComboBox cboReward = ((Itim.Framework.Silverlight.UI.Controls.ComboBox)sender);
int row = (int)cboReward.GetValue(Grid.RowProperty);
if (Model.ListRewardRule.Count > 0)
{
var rewardGroups = Model.RewardGroupAndTier.RewardGroups;
if(rewardGroups.Count > 1)
{
cboReward.ItemsSource = Model.ListRewardRule[row];
}
}
}
The way I understand this is, you have a “grid” (I suppose you mean a DataGrid) and each row in the grid has, among other things, a set of combo boxes.
What I’d do is bind that grid to an ObservableCollection of a custom class, call it CustomRowClass. CustomRowClass should have an ObservableCollection that will be bound to the combo boxes.
The magic is to define a DataTemplate for CustomRowClass. Once you set up the XAML this way, all you need to do is create an instance of CustomRowClass, and then add it to the ItemsSource of the grid.
Related
I have a page with two parts :
A Listview with itemsource binded with a class, ObservableCollection "Patients" loaded with "Patient" class.
Under the Listview are Textboxes binded with the selecteditem Patient. Everything works without writing any code in the page, except in selection_changed to scroll to selected item.
A second Listview must display the details "Visites" from the selected "Patient".
The application works in a MVVM Framework with a Viewmodel containing the properties for the page.
The problem is to make the relation between the two ListView. I tried first building the second list "Visites" in the NotifyPropertyChanged event :
if (Patient.ID > 0)
{
LoadVisite(Patient.ID); // fill the details list "Visites"
NotifyPropertyChanged("Visites");
}
No delail is shown when "Patient" is selected.
I tried another solution inserting the list of details in the master class "Patient like this :
public Class Patient
...
public ObservableCollection<ClsVisite> Visites
{
get
{
return _visites;
}
set
{
_visites = value;
}
}
// WDABase class to open the database and load data connection
WDABase wd = new WDABase();
wd.LoadListeVisites(ID, _visites); //ID is the relation beween the two tables
}
}
Now I try to create the Listview detail itemsource in the XAML like this :
<ListView Name="ListeVisites" ItemsSource="{Binding Path=Patient.Visites}" SelectedItem="{Binding Visite}">
No details where shown.
The unique solution I found was to add some code in the selection_changed event of the master Listview like this (in this case the listviews are in two different frame) :
private void ListePatients_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ListePatients.ScrollIntoView(ListePatients.SelectedItem);
if(ListePatients.SelectedItem != null)
{
var w1 = ((System.Windows.FrameworkElement)App.Current.MainWindow.Content).Parent;
Frame pageVisite = (w1 as MainWindow).Visit;
var w2 = (System.Windows.FrameworkElement)pageVisite.Content;
ListView Lv = (w2 as Visite).ListeVisites;
Lv.ItemsSource = (ListePatients.SelectedItem as ClsPatient).Visites;
}
}
And it's works but is there another elegant solution to bind the itemsource of the details Listview ?
Thanks for helping me.
Jean-Marie
I found the solution to my problem.
The Reason the details were not displayed is because the frame used for the listview is not common with the master frame.
I discover that when I display the details frame the "Patient" object contains only null. I Don't know why but I read about sharing ViewModel and now I use a common one in App.xaml and I Don't need to specify the itemsource for the details Listview in code. I put in XAML itemsource={Binding Patient.Visites} and everything works.
in my WPF application, I have a DataGrid, which is bound to an ObservableCollection.
<DataGrid x:Name="DataGridTeilnehmer" HorizontalAlignment="Left" VerticalAlignment="Top" CellEditEnding="DataGridTeilnehmer_CellEditEnding" AutoGenerateColumns="False" SelectionMode="Single">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Teilnehmer" CellEditingTemplate="{StaticResource TeilnehmerEditTemplate}" CellTemplate="{StaticResource TeilnehmerCellTemplate}" />
<DataGridComboBoxColumn Header="Pass" />
...
The DataGridComboBoxColumn shall be filled with individual values for each row. The values depend on the entry of the first column. So, I would like to set the data in the CellEditEnding event like this:
private void DataGridTeilnehmer_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
if (!commiting)
{
commiting = true;
DataGridTeilnehmer.CommitEdit(DataGridEditingUnit.Row, false);
commiting = false;
// check, whether it is the first column that has been edited
if (...)
// get the list<string> for the combobox depending on the edited content
// get the combobox of the current row and bind the calculated list<string> to it
}
}
}
How can I do this?
EDIT: An example of what I am trying to achieve.
I have list of customers, which have individual tickets each. When the customer has been chosen in the first column, I want to load the ticket-list this customer has and load it into the next column - the combobox column.
Thanks in advance,
Frank
If you bound your datagrid to an ObservableCollection and your object implements INotifyPropertyChanged you can achieve what you need without using cell editending event.
In your model just check the value of your first column then set others columns values:
private string _firstColumn;
public string FirstColumn
{
get { return _firstColumn; }
set {
_firstColumn = value;
if(value == ...)
//set other properties
...
//notify the change
OnPropertyChanged("FirstColumn"); }
}
when your datagridrow lost focus all the new values will be notified to the datagrid
I would like to bring a row of my data grid into view programatically. I have a more than 100 rows. When I create a row(which I am doing by adding an item to a observable collection) I would like that new row to be selected and bring that into view. I was able to select the new row in my code but could not do the scrolling. More over I want the first cell of the row to be in edit mode so that the user can input text. I am following MVVM pattern for the application and would like to keep zero code in my views. How Can I achieve this?
Any help or suggestion will be appreciated....
Update:
This what I did in my XAML
<telerik:RadGridView ItemsSource="{Binding AllPartClasses}"
SelectedItem="{Binding SelectedPartClassViewModel, Mode=TwoWay}"
SelectionMode="Single" IsSynchronizedWithCurrentItem="True">
in my view model I did this
void AddNewPartClassExecute()
{
PartClass newPartClass = new PartClass();
PartClassViewModel tempPartClass = new PartClassViewModel(newPartClass);
tempPartClass.IsInValid = true;
AllPartClasses.Add(tempPartClass);
SelectedPartClassViewModel = tempPartClass;
Global.DbContext.PartClasses.AddObject(newPartClass);
//OnPropertyChanged("AllPartClasses");
}
public PartClassViewModel SelectedPartClassViewModel
{
get
{
return _selectedPartClassViewModel;
}
set
{
_selectedPartClassViewModel = value;
OnPropertyChanged("SelectedPartClassViewModel");
}
}
It did not work for me.
For the regular WPF DataGrid you can use ScrollIntoView. In your view hookup the SelectionChanged event to the following in your view code-behind cs file.
private void OnSelectionChanged( object sender, SelectionChangedEventArgs e )
{
Selector selector = sender as Selector;
DataGrid dataGrid = selector as DataGrid;
if ( dataGrid != null && selector.SelectedItem != null && dataGrid.SelectedIndex >= 0 )
{
dataGrid.ScrollIntoView( selector.SelectedItem );
}
}
When following MVVM pattern you should not do a UI-specific stuff like scrolling from a code.
Solution would be simple - just bind DataGrid.SelectedItem to a property in ViewModel and when adding a new item in the items collection just update a property bound to SelectedItem so it would reference to just added item and data grid should select an appropriate row automatically.
<DataGrid
ItemsSource="{Binding UnderyingItemsCollection}"
SelectedItem="{Binding RecentlyAddedItem, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="True">
I have a DataGridCell that contains a ComboBox.
I want, that when I fire 'SelectionChanged' event of it, a CollectionViewSource of a different column (eventually - at runtime, cell) CellEditingTemplate's Resources should be populated with data according to the selected value for this row.
Maybe DataTrigger, ActionTrigger, EventTrigger, maybe by code, XAML I don't care, I just need a solution.
Thanks a lot!
Related: Accessing control between
DataGridCells, dynamic cascading
ComboBoxes
If I understand your question right, you will fill the contents of a combobox in a cell based on the selection of a combobox in another cell that is in the same row of the DataGrid.
If yes:
First Solution (IMO the preferable)
Make a ViewModel that represents the rows data (a simple wrapper around your data object). Bind the ItemsSource-property of the destination ComboBox to a IEnumerable-property that you provide from your viewmodel.
Bind the SelectedItem from the source-ComboBox to another property of your ViewModel. Every time this source-property changes in your ViewModel, you change the contents of the list that is provided by the ViewModel.
Use for the desintation (list) property a ObservableCollection<T>. The source property is up to you.
Here is an approximately example. I call the class VM (for ViewModel) but this changes nothing on your current solution. MVVM can also be used partial.
public class DataObjectVM : DependencyObject {
public static readonly DependencyProperty SelectedCategoryProperty =
DependencyProperty.Register("SelectedCategory", typeof(CategoryClass), typeof(DataObjectVM), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,delegate (DependencyObject d,DependencyPropertyChangedEventArgs e){
((DataObjectVM)d).SelectedCategoryChanged(e);
}));
ObservableCollection<ItemClass> _items=new ObservableCollection<ItemClass>();
void SelectedCategoryChanged(DependencyPropertyChangedEventArgs e) {
// Change here the contents of the _items collection.
// The destination ComboBox will update as you desire
// Do not change the _items reference. Only clear, add, remove or
// rearange the collection-items
}
// Bind the destination ComboxBox.ItemsSource to this property
public IEnumerable<ItemClass> DestinationItems {
get {
return _items;
}
}
// Bind to this property with the source ComboBox.SelectedItem
public CategoryClass SelectedCategory {
get { return (CategoryClass)GetValue(SelectedCategoryProperty); }
set { SetValue(SelectedCategoryProperty, value); }
}
}
Add a constructor to this class that takes your data object and make some wrapper properties to the rest the properties you need to provide in the DataGrid. If they are alot, you can also make one property that provides your data object and the bind directly to it. Not nice, but it will do the job.
You also can (must) pre-initialize the SelectedCategory with data from your business object. Do this also in the constructor.
As a ItemsSource for the DataGrid you give an IEnumerable of the DataObjectVM-class that wrapps all items you want to show.
Alternative way with VisualTreeHelper
If you want to do it manual, register in the code behind a handler for the ComboBox.SelectionChangedEvent and change then the ItemsSource of the destination ComboBox manual. The business-object you will get with the EventArgs. The destination ComboBox you must search in the visual tree (Use the VisualTreeHelper). The events can be wired also if you use the DataGridTemplateColumn class and add a DataTemplate with the corresponding ComboBoxes.
But I think this is realy not very simple to do and can be error prone. The above solution is much easier.
Here is the code you propably are looking for:
private void CboSource_SelectionChanged(object sender, SelectionChangedEventArgs e) {
ComboBox cbo = (ComboBox)sender;
FrameworkElement currentFe = VisualTreeHelper.GetParent(cbo) as FrameworkElement;
while (null != currentFe && !(currentFe is DataGridRow)) {
currentFe = VisualTreeHelper.GetParent(currentFe) as FrameworkElement;
}
if (null != currentFe) {
List<ComboBox> list = new List<ComboBox>();
FindChildFrameworkElementsOfType<ComboBox>(currentFe,list);
// Requirement 1: Find ComboBox
foreach (ComboBox cboFound in list) {
if (cboFound.Name == "PART_CboDestination") {
// This is the desired ComboBox
// Your BO is available through cbo.Found.DataContext property
// If don't like to check the name, you can also depend on the
// sequence of the cbo's because I search them in a deep search
// operation. The sequence will be fix.
}
}
List<DataGridCell> cells = new List<DataGridCell>();
FindChildFrameworkElementsOfType<DataGridCell>(currentFe,cells);
// Requirement 2: Find Sibling Cell
foreach (DataGridCell cell in cells) {
// Here you have the desired cell of the other post
// Take the sibling you are interested in
// The sequence is as you expect it
DataGridTemplateColumn col=cell.Column as DataGridTemplateColumn;
DataTemplate template = col.CellTemplate;
// Through template.Resources you can access the CollectionViewSources
// if they are placed in the CellTemplate.
// Change this code if you will have an edit cell template or another
// another construction
}
}
}
void FindChildFrameworkElementsOfType<T>(DependencyObject parent,IList<T> list) where T: FrameworkElement{
DependencyObject child;
for(int i=0;i< VisualTreeHelper.GetChildrenCount(parent);i++){
child = VisualTreeHelper.GetChild(parent, i);
if (child is T) {
list.Add((T)child);
}
FindChildFrameworkElementsOfType<T>(child,list);
}
}
And this is the markup I used:
<DataGrid.Columns>
<DataGridTemplateColumn Header="Source" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="PART_CboSource" SelectionChanged="CboSource_SelectionChanged" ItemsSource="!!YOUR ITEMS SOURCE!!" SelectedItem="{Binding Category}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Destination">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="PART_CboDestination"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
Accessing the CollectionViewSource
To access the CollectionViewSource, put it into the resources section of the corresponding DataTemplate, not of the panel, then you will have direct access to them. IMO is this location anyway more appropriate than the resources-container of the grid.
If you dont't want to do this, check the state of the following post:
How to get logical tree of a DataTemplate
I have a TabControl whose items are bound to an ObservableCollection:
<TabControl ItemsSource="{Binding MyObservableCollection}" />
The tabs are added and removed as expected as items are added and removed from the collection. However, the SelectedItem reverts to -1 (meaning there is no selected tab) whenever the collection is empty. Then, when an item is added, the SelectedItem stays at -1 and the new tab is not selected.
How do I make the TabControl select the new tab whenever an item is added to the empty collection?
There might be an easier way, but you could hook the collection changed event on the ObservableCollection in your VM and set the SelectedItem property to the new item (assuming you have the selected item bound to a property on the VM).
What you can do is to subscribe for TabControl.ItemContainerGenerator.StatusChanged event and if the status is ContainersGenerated and the SelectedIndex of TabControl is -1, then to make the SelectedIndex of TabControl 0;
// peopleCollection is an ObservableCollection<Person>
People peopleCollection = new People();
public Window1()
{
InitializeComponent();
// MyTabControl is an instance of TabControl
MyTabControl.ItemsSource = peopleCollection;
MyTabControl.ItemContainerGenerator.StatusChanged += new EventHandler(ItemContainerGenerator_StatusChanged);
}
void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if((sender as ItemContainerGenerator).Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated && MyTabControl.SelectedIndex == -1)
{
MyTabControl.SelectedIndex = 0;
}
}
There are 3-rd party solutions that has this functionality out of the box. Telerik's RadTabControl selects the first item whenever the collection changes its state from empty to "containing single item".
Try the demo here: http://demos.telerik.com/silverlight/#TabControl/AddingAndRemovingTabs
Note: It is a SL demo, but it works the same in WPF.
If you are looking for a pure MVVM implementation then, add a Index property to the ViewModel and on the CollectionChanged you can set Index=0 if there is no items inside. And in the XAML you can bind that Index as below
<TabControl ItemsSource="{Binding MyObservableCollection}" SelectedIndex="{Binding Index}" />
you're best bet is to probably overwrite the "OnTabAdded" functionality to check if a new one (first one) is added and then setting the SelectedItemIndex to 0;
since you are using ObservableCollection, you know when your collection changes, so I'd subscribe to the changed event form the collection and check the number of items in it.
I had the same problem and managed to fix it by binding the selected item to the first item in the dynamic list.
<TabControl ItemsSource="{Binding MyObservableCollection}" SelectedItem="{Binding MyObservableCollection.First}" />
Worked for me :)
<TabControl ItemsSource="{Binding MyObservableCollection}" SelectedItem="{Binding MyObservableCollection[0]}" />