Reputation: 57
Background
There is a ListView which contains 2 columns: File Column (string) and Worksheet Column (ComboBox).
For example, the list is "- None -", "1", "2", "3", "4", "5", "6", "7", "8"... When every ComboBox starts as "- None -", every ComboBox has all list items to choose from. But when a ComboBox picks for example "1", all other ComboBoxes no longer have it in their drop-down list. If "1" is then switched to anything else, ComboBoxes can again select it. So, only not selected items and "- None -" must show up in the drop-down list.
Each WorksheetItem has properties like ID (int), Name (string) and Selected (bool).
Last and most important: a ComboBox which selected for example "1" should still have "1" in its drop-down list.
To sum up in a short example, ComboBoxes are "BOY"s, WorksheetColumns are "girl"s. Imagine BOY1 choosing a girlfriend: he can be single ("- None -"), choose girl1 (because she is not CURRENTLY chosen by any BOY), girl2 (the same), but not girl3 (because she was chosen by BOY2). At the same time, BOY2 can choose to become single ("- None -"), switch to girl1 or girl2, or stay with girl3. The task is to include girl3 in the list of options only for BOY2.
Since the number of ComboBoxes is just as undefined as WorksheetColumns, I cannot create variables in XAML or C#.
Previously
I was advised to put a method in a class which contains WorksheetItems to get a subcollection of WorksheetItems. The method returns all not Selected items, an item with ID equal to 0 and the item selected by the current ComboBox (specified by the argument "keepColumn"):
public List<WorksheetColumn> GetWorksheetColumnHeaders(int keepColumn)
{
return WorksheetColumns.Where(header => header.ID == 0 || header.Selected == false || header.ID == keepColumn).ToList();
}
The problem is: I don't know where to call this method. I've read that it's impossible to call a method from XAML but the result of the method should be set as ItemsSource to multiple ComboBoxes (each of them must have a bit different ItemsSource because of that "keepColumn" argument which keeps the WorksheetItem selected by this very ComboBox - that's the goal).
I have XAML, ComboBox_SelectionChanged event and OnWorksheetItemPropertyChanged (happens when "Selected" gets changed).
Code
Right now my XAML uses a property, which does not return the needed item, instead of a method:
<ListView Grid.Row="0" Name="listView" IsSynchronizedWithCurrentItem="True" SelectionMode="Single" Margin="10" ItemsSource="{Binding ImportColumns}" >
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header="File column" DisplayMemberBinding="{Binding FileColumnHeader}"/>
<GridViewColumn Header="Worksheet column" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox VerticalAlignment="Center" DataContext="{Binding DataContext,RelativeSource={RelativeSource AncestorType={x:Type ListView}}}" ItemsSource="{Binding ListOfWorksheetColumns.UnselectedWorksheetColumns, UpdateSourceTrigger=PropertyChanged}" SelectionChanged="ComboBox_SelectionChanged" SelectedIndex="0">
</ComboBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
My main ViewModel:
public class ImportManagerViewModel : BaseViewModel
{
public List<ImportColumn> ImportColumns { get; set; }
public ListOfWorksheetColumnHeaders ListOfWorksheetColumns { get; set; }
public bool IsAnyColumnImported
{
get;
set;
}
public void OnImportItemPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
IsAnyColumnImported = ImportColumns.Any(x => x.TargetColumnIndex != 0);
}
public void OnWorksheetItemPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
ListOfWorksheetColumns.UnselectedWorksheetColumns = new ObservableCollection<WorksheetColumn>(ListOfWorksheetColumns.WorksheetColumns.Where(header => header.ID == 0 || header.Selected == false /*|| header.ID == 1*/));
}
}
My ListOfWorksheetColumnHeaders class (contains the collection, a subcollection and the method):
public class ListOfWorksheetColumnHeaders : BaseViewModel
{
public ObservableCollection<WorksheetColumn> WorksheetColumns { get; set; } = new ObservableCollection<WorksheetColumn>();
public ObservableCollection<WorksheetColumn> UnselectedWorksheetColumns
{
get;
set;
}
public List<WorksheetColumn> GetWorksheetColumnHeaders(int keepColumn)
{
return WorksheetColumns.Where(header => header.ID == 0 || header.Selected == false || header.ID == keepColumn).ToList();
}
}
My WorksheetColumn class:
public class WorksheetColumn : BaseViewModel
{
public int ID;
public string ColumnName { get; set; }
public bool Selected
{
get;
set;
}
public override string ToString()
{
return ColumnName;
}
}
As of now
The only thing that I need - make the selected WorksheetItem stay in the drop-down list of the ComboBox that selected it. Otherwise, the ComboBox loses this item from the drop-down list immediately because its "Select" property becomes true. Not important: after it is not longer selected by the ComboBox, its "Select" property becomes false, and it appears in the list but the ComboBox has nothing currently selected; in other words, if you select anything, it just clears the selection of the combobox.
Upvotes: 0
Views: 134
Reputation: 306
try to use converter
[ValueConversion(typeof(List<WorksheetColumn>), typeof(List<WorksheetColumn>))]
public class ListFilterConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
List<WorksheetColumn> worksheets = value as List<WorksheetColumn>;
int keepColumn = (int)parameter;
return worksheets.Where(header => header.ID == 0 || header.Selected == false || header.ID == keepColumn).ToList();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML is..
<ListView Grid.Row="0" Name="listView" IsSynchronizedWithCurrentItem="True" SelectionMode="Single" Margin="10" ItemsSource="{Binding ImportColumns}" >
<ListView.Resources>
<local:ListFilterConverter x:Key="myConverter" />
<sys:Int32 x:Key="keepcolumn1">1</sys:Int32>
</ListView.Resources>
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header="File column" DisplayMemberBinding="{Binding FileColumnHeader}"/>
<GridViewColumn Header="Worksheet column" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox VerticalAlignment="Center" DataContext="{Binding DataContext,RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
ItemsSource="{Binding ListOfWorksheetColumns.UnselectedWorksheetColumns, Converter={StaticResource myConverter}, ConverterParameter={StaticResource keepcolumn1}}"
SelectionChanged="ComboBox_SelectionChanged" SelectedIndex="0">
</ComboBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Upvotes: 0
Reputation: 4943
You could change your method into a property, which allows for Binding
:
public class ImportColumn {
private int keepColumn;
public List<WorksheetColumn> WorksheetColumnHeaders => WorksheetColumns.Where(header => header.ID == 0 || header.Selected == false || header.ID == keepColumn).ToList();
}
You would have to put that in a new class and bind each ItemsSource
to this property like this:
<ComboBox ItemsSource="{Binding WorksheetColumnHeaders}"/>
But that is not the right way to go about bindings as you should not mix your view and your data, you should have a look at the MVVM pattern and look at ICommand
as suggested by other answers.
Upvotes: 0
Reputation: 70671
The problem is: I don't know where to call this method
Most likely, you shouldn't call it at all. I base that on this statement:
the result of the method should be set as ItemsSource
That tells me that you have these items controls (e.g. ComboBox
) where what you should be doing is binding the WorksheetColumns
collection indirectly, via a CollectionViewSource
object. The CollectionViewSource
object will allow you to filter the view, which you can do by providing the predicate you currently have in the method, i.e. header => header.ID == 0 || header.Selected == false || header.ID == keepColumn
.
It's not clear where the keepColumn
parameter comes from, but if it's not a property in a view model somewhere already it probably should be. In any case, once you've studied the collection binding model more and understand how CollectionViewSource
works, I expect you'll be able to figure out how to get the keepColumn
value in your filter without too much trouble.
If after studying the relevant documentation and making some attempt to solve the problem, you're still unable to figure out how to get it to work and your coworkers are unable to help you, please feel free to post a new question. But in that question, make sure you include a good Minimal, Reproducible Example that shows clearly what you've tried, with a detailed explanation of what that code does, what you want it to do instead, and what specifically you need help with.
By the way…
I've read that it's impossible to call a method from XAML
Well, that's not true. There are at least four key ways that methods can be "called from XAML" (depending on what one exactly means):
ICommand
binding. Most places you can provide an ICommand
, you also have the option of passing a CommandParameter
. This is an object
reference passed to your ICommand
's CanExecute()
and Execute()
methods. You can pass multiple arguments by passing an array. The ICommand.Execute()
method is called when the command is executed for any reason.IValueConverter
and IMultiValueConverter
. These also take parameters. The relevant methods in the interfaces are called when value conversion is required, generally in the context of a binding.ObjectDataProvider
to call any method that can return any type of value. You can call a method on an instance, or a static method. You can pass parameters to the method. You can even call a constructor to create an instance of an object. (Warning: it might be tempting to try to shoe-horn this into your scenario…don't do it! It likely wouldn't work anyway, since ObjectDataProvider
is a one-shot sort of thing. But even if you hacked around that somehow, it would definitely be the wrong way to approach this scenario.)Upvotes: 2