Get item from CollectionViewSource using index number - c#

I'm using CollectionViewSource as ItemSource in DataGrid
<DataGrid
ItemsSource="{Binding CollViewSource.View}"
SelectedIndex="{Binding IndexNumber}"
...
and the CollectionViewSource is bound to ObservableCollection in ViewModel
private ObservableCollection<LevelModel> mLevelSource;
public ObservableCollection<LevelModel> LevelSource
{
get
{
mLevelSource = mLevelSource ?? new ObservableCollection<LevelModel>();
return mLevelSource;
}
}
public CollectionViewSource CollViewSource { get; set; }
Model
public class LevelModel : BaseViewModel
{
public string Level_Title { get; set; }
...
In Constructor
CollViewSource = new CollectionViewSource();
CollViewSource.Source = LevelSource;
I have Button inside DataGrid
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Command="{Binding DataContext.ViewCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}"
Content="View" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
What I want, when i click the Button .i.e ViewCommand it should fetch Level_Title or some other item by Index Number
private ICommand mViewCommand;
public ICommand ViewCommand
{
get
{
if (mViewCommand == null)
{
mViewCommand = new DelegateCommand(delegate ()
{
int indexNumber = IndexNumber;
//string title = // logic should go here
});
}
return mViewCommand;
}
}
For example when index number is 3 then it should fetch the item that exist on 3rd index
Note: I don't want to involve SeletedItem

Try the following on your CollectionView:
LevelModel lm = CollViewSource.View.Cast<LevelModel>().ToArray()[indexNumber];
string title = lm.Level_Title;

Related

Error on binding table value to Combobox - wpf

I have a combobox which should bind the data dynamically from the database .
The source of the combobox is a observable collection.
Steps I followed:
Declared a combobox :
<ComboBox ItemsSource="{Binding populatecombobox.modeltogetusername }" Width="155" Margin="18,15,618,0"/>
Created a class to get the data from the database :
public class populatetab2combobox
{
public ObservableCollection<comboboxdata> modeltogetusername { get; set; }
public void getdatausinglinq()
{
using (Operations_Productivity_ToolEntities context = new Operations_Productivity_ToolEntities())
{
var a1 = from t1 in context.Test_ImportedAuditdata
select t1;
if (modeltogetusername == null)
modeltogetusername = new ObservableCollection<comboboxdata>();
foreach (var a in a1.GroupBy(x => x.username).Select(x => x.FirstOrDefault()))
{
modeltogetusername.Add(new comboboxdata
{
username = a.username
});
}
}
}
}
Instantiating the above mentioned class in viewmodel
public class ViewModel: INotifyPropertyChanged {
private populatetab2combobox _populatecombobox = new populatetab2combobox();
public populatetab2combobox populatecombobox {
get {
return _populatecombobox;
}
set {
if (value != _populatecombobox) {
_populatecombobox = value;
OnPropertyChanged("populatecombobox");
}
}
}
public ViewModel() {
_populatecombobox.getdatausinglinq();
}
}
The expected output is :
Ren1
Ren2
The actual output is
Namespace.Model.comboxdata
Namespace.Model.comboxdata
You are getting the output of the ToString() method and you are binding to instances of comboboxdata class and not the username inside of it.
You have 2 options.
First you can change your xaml to this notice how we bind to the property in the item template.
<ComboBox ItemsSource="{Binding populatecombobox.modeltogetusername }" Width="155" Margin="18,15,618,0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding username}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Second you can override the ToString() method on comboboxdata to return the username

search value in listview itemsource by index number wpf mvvm

I'm using Wpf MVVM, if i know the index number of item/row then how can i search the value in listview/itemsource by specific index number.
Note: i can get index number, index number will already be known.
below is the xaml code for listview
<ListView
Grid.Row="1"
ItemContainerStyle="{StaticResource FileItemStyle}"
ItemsSource="{Binding BarCode, IsAsync=True}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedIndex="{Binding SelectedIndex}"
SelectedItem="{Binding SelectBarCode,
UpdateSourceTrigger=PropertyChanged}"
SelectionMode="Single"
Style="{StaticResource ListItemsMain}"
and ObservableCollection for itemsource
private ObservableCollection<BarCodeModel> mBarCode = null;
public ObservableCollection<BarCodeModel> BarCode
{
get
{
mBarCode = mBarCode ?? new ObservableCollection<BarCodeModel>();
return mBarCode;
}
}
and below code is for model
public class BarCodeModel
{
public int BarCodeEntry_ID { get; set; }
public string BarCodeEntry_Title { get; set; }
and below is the command where i want to put my logic
private ICommand mSearchValueByIndexNumberCommand;
public ICommand SearchValueByIndexNumberCommand
{
get
{
if (mSearchValueByIndexNumberCommand == null)
{
mSearchValueByIndexNumberCommand = new DelegateCommand(delegate ()
{
// search BarCodeEntry_ID in BarCode where SelectedIndex is 5 (or other value)
});
}
return mSearchValueByIndexNumberCommand;
}
}
As in your XAML you bind your ListView.ItemsSource to BarCode of your ViewModel, but also bind ListView.SelectedItem and ListView.SelectedIndex to SelectBarCode and SelectedIndex, now, when you select some Item in a ListView, it (ListView) will update values of SelectBarCode and SelectedIndex in your ViewModel.
So, you can access your current selection with SelectBarCode or BarCode[SelectedIndex].
Below is answer to my question, a special thanks to #vasily.sib
private ICommand mSearchValueByIndexNumberCommand;
public ICommand SearchValueByIndexNumberCommand
{
get
{
if (mSearchValueByIndexNumberCommand == null)
{
mSearchValueByIndexNumberCommand = new DelegateCommand(delegate ()
{
int BarCodeId = BarCode[SelectedIndex].BarCodeEntry_ID;
});
}
return mSearchValueByIndexNumberCommand;
}
}

WPF MVVM creating multiple datagrids and bind items to them

I have met following problem in WPF binding.
I need to load objects from XML file and create list of loaded items in listbox, and when listbox item is selected then display suitable set of objects.
I can do it in 'code behind' style, but I really want to do it in proper MVVM way.
My Matrixes class is generated by xsd2code from which contains:
List<CorrectionMatrixType> correctionMatrixField;
and follows
public partial class CorrectionMatrixType {
public MatrixType A {get; set;}
public MatrixType B {get; set;}
public MatrixType C {get; set;}
... }
How can I create 'dynamically'something like Grid with three DataGrids by Viewmodel and bind each matrix (A,B,C) to them which content will change depends of value selected in listbox? I know that to bind my MatrixType to DataGrid i have to use ValueConverter to convert my object to two-dimensional array.
Maybe I have to admit I am using MVVM Light.
Please, any suggestions?
I would use the INotifyPropertyChanged Interface. Here is a small example (not exactly your case, but enough to show the principle, I think):
MatrixType class:
public class MatrixType
{
public string Name { get; set; }
public string Width { get; set; }
public string Height { get; set; }
}
Xaml:
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Items}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedItem}"></ListBox>
<Grid Grid.Column="1">
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding SelectedItem.Name}" Height="30"/>
<TextBox Text="{Binding SelectedItem.Height}" Height="30"/>
<TextBox Text="{Binding SelectedItem.Width}" Height="30"/>
</StackPanel>
</Grid>
</Grid>
MainViewModel.cs:
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
var list = new List<MatrixType>
{
new MatrixType {Height = "233", Name = "A", Width = "133"},
new MatrixType {Height = "333", Name = "B", Width = "233"},
new MatrixType {Height = "433", Name = "C", Width = "333"}
};
Items = new ObservableCollection<MatrixType>(list);
}
private MatrixType _selectedItem;
public MatrixType SelectedItem
{
get => _selectedItem;
set { _selectedItem = value; OnPropertyChanged(); }
}
public ObservableCollection<MatrixType> Items { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainViewModel.cs (when using MVVM Light):
public class MainViewModel : ObservableObject
{
public MainViewModel()
{
var list = new List<MatrixType>
{
new MatrixType {Height = "233", Name = "A", Width = "133"},
new MatrixType {Height = "333", Name = "B", Width = "233"},
new MatrixType {Height = "433", Name = "C", Width = "333"}
};
Items = new ObservableCollection<MatrixType>(list);
}
private MatrixType _selectedItem;
public MatrixType SelectedItem
{
get => _selectedItem;
set { _selectedItem = value; RaisePropertyChanged(); }
}
public ObservableCollection<MatrixType> Items { get; set; }
}
I wrote solution by myself, I don't know if it is good MVVM solution.
I re-write my XSD so MatrixType becomes SimpleMatrix, and now:
XAML:
<ListBox Margin="5,20,0,5" ItemsSource="{Binding CorrectionMatrixes}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<command:EventToCommand Command="{Binding SelectionChangedCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
<i:EventTrigger EventName="Loaded">
<command:EventToCommand Command="{Binding ListBoxLoadedCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
<DataGrid CanUserAddRows="False" HeadersVisibility="None" ItemsSource="{Binding CorrectionMatrixA, Converter={StaticResource MatrixToArray }}"/>
And in my viewmodel:
public RelayCommand<SelectionChangedEventArgs> SelectionChangedCommand => new RelayCommand<SelectionChangedEventArgs>(SelectionChanged);
public RelayCommand<RoutedEventArgs> ListBoxLoadedCommand => new RelayCommand<RoutedEventArgs>(ListBoxLoaded);
private string CorrectionMatrixName { get; set; }
private void ListBoxLoaded(RoutedEventArgs obj)
{
if (obj.Source is ListBox listBox)
{
listBox.SelectedIndex = 0;
}
}
private void SelectionChanged(SelectionChangedEventArgs obj)
{
if (obj.AddedItems.Count <= 0) return;
if (obj.AddedItems[0] is CorrectionMatrix matrix)
{
CorrectionMatrixName = matrix.name;
}
RaisePropertyChanged(() => CorrectionMatrixA);
}
public SimpleMatrix CorrectionMatrixA
{
get
{
try
{
var x = Matrixes.Correction.Where(a => a.name == CorrectionMatrixName)
.Select(a => a.A).Single();
return x;
}
catch (InvalidOperationException)
{
return null;
}
}
}
Matrixes are loaded by:
Matrixes = settingsLoader.LoadMatrixes(Properties.Resources.MatrixesSettings);
How does it all works:
when user control is loaded => selected index on listbox is setting to zero
When selected item on listbox is changed it fires event that changes CorrectionMatrixName
Binding properties returns suitable matrix finding it in array by name
I don't post Converter code - it doesn't matter here.
Thats full, my own solution that worked for me. I hope it will helps other people

View to ViewModel to Settings

Is it possible to refactor the currentDevices into a collection?
Basically, I have three comboboxes in which the selectedvalue is bound to currentDevices. then the currentDevices are taken from a settings file.
View
<ComboBox ItemsSource="{Binding availableDevices}"
SelectedValue="{Binding currentDevice1}">
</ComboBox>
<ComboBox ItemsSource="{Binding availableDevices}"
SelectedValue="{Binding currentDevice2}">
</ComboBox>
<ComboBox ItemsSource="{Binding availableDevices}"
SelectedValue="{Binding currentDevice3}">
</ComboBox>
ViewModel
public string currentDevice1 {
get
{
return SampleSettings.Default.Device1;
}
set
{
SampleSettings.Default.Device1 = value;
}
}
public string currentDevice2
{
get
{
return SampleSettings.Default.Device2;
}
set
{
SampleSettings.Default.Device2 = value;
}
}
public string currentDevice3
{
get
{
return SampleSettings.Default.Device3;
}
set
{
SampleSettings.Default.Device3 = value;
}
}
I'd write a DeviceOptionViewModel, and give the main viewmodel an ObservableCollection.
DeviceOptionViewModel.cs
public class DeviceOptionViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _currentDevice;
public String CurrentDevice {
get { return _currentDevice; }
set {
_currentDevice = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(CurrentDevice));
}
}
// Parent event assigns this to his own availableDevices
// when he creates this.
public IEnumerable AvailableDevices { get; set; }
}
Main VM:
public ObservableCollection<DeviceOptionViewModel>
CurrentDevices { get; private set; }
= new ObservableCollection<DeviceOptionViewModel>();
XAML:
<ItemsControl
ItemsSource="{Binding CurrentDevices}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- DataContext here is DeviceOptionViewModel. We gave it its
own reference to AvailableDevices to simplify binding. -->
<ComboBox
ItemsSource="{Binding AvailableDevices}"
SelectedValue="{Binding CurrentDevice}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Back to main viewmodel:
protected void PopulateCurrentDevices(IEnumerable<String> stringsFromWherever)
{
CurrentDevices.Clear();
foreach (var device in stringsFromWherever)
{
var dovm = new DeviceOptionViewModel() {
CurrentDevice = device,
AvailableDevices = this.availableDevices
};
dovm.PropertyChanged += DeviceOptionViewModel_PropertyChangedHandler;
CurrentDevices.Add(dovm);
}
}
protected void DeviceOptionViewModel_PropertyChangedHandler(object sender,
PropertyChangedEventArgs e)
{
var dopt = sender as DeviceOptionViewModel;
if (e.PropertyName == nameof(DeviceOptionViewModel.CurrentDevice))
{
// Do stuff
}
}
So you populate and repopulate CurrentDevices in your viewmodel as needed, and the UI will magically appear if all the notifications are done correctly.
If you create a new ObservableCollection and assign that to the CurrentDevices property, you'll need to raise PropertyChanged(nameof(CurrentDevices)) on the main viewmodel. I made the setter private to avoid having to implement that detail. If it's not a huge collection, may as well just Clear() and Add() on the same old instance.

Filtering ObservableCollection with ICollectionView

I have ObservableCollection binded to dataGrid and now I want to filter the presented data I see that I need to use ICollectionView but I am not sure how to add ICollectionView with my MVVM pattern.
My code simplified looks following:
public class MainViewModel : ViewModelBase , IBarcodeHandler
{
public ObservableCollection<TraceDataItem> TraceItemCollectionViewSource { get; set; }
}
My XAML
<Window xmlns:controls="clr-namespace:Mentor.Valor.vManage.RepairStation.Controls"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
<DataGrid Grid.Row="2" ColumnWidth="*" ItemsSource="{Binding TraceItemCollectionViewSource , Mode=TwoWay , UpdateSourceTrigger=PropertyChanged}" RowStyle="{StaticResource TraceRowStyle}" IsReadOnly="True" Name="TraceDataGrid" Margin="5,5,5,5" Padding="5,5,5,5" AutoGenerateColumns="False">
</Window>
How I can add ICollectionView here in order to apply filtering to the view?
You would need to:
public class MainViewModel : ViewModelBase, IBarcodeHandler
{
public ICollectionView TraceItemCollectionView
{
get { return CollectionViewSource.GetDefaultView(TraceItemCollectionViewSource); }
}
public ObservableCollection<TraceDataItem> TraceItemCollectionViewSource { get; set; }
}
then, somewhere in the code (maybe in the constructor) add your filter:
TraceItemCollectionView.Filter = o =>
{
var item = (TraceDataItem) o;
//based on item, return true if it should be visible, or false if not
return true;
};
And, in XAML, you would need to change the binding to TraceItemCollectionView property.
You may invoke the Filter callback from a Command and expose the View property from CollectionViewSource :
public class ViewModel: INotifyPropertyChanged
{
private CollectionViewSource data = new CollectionViewSource();
private ObservableCollection<Child> observableChilds = new ObservableCollection<Child>();
public ViewModel()
{
var model = new Model();
model.ChildList.Add(new Child { Name = "Child 1" });
model.ChildList.Add(new Child { Name = "Child 2" });
model.ChildList.Add(new Child { Name = "Child 3" });
model.ChildList.Add(new Child { Name = "Child 4" });
//Populate ObservableCollection
model.ChildList.ToList().ForEach(child => observableChilds.Add(child));
this.data.Source = observableChilds;
ApplyFilterCommand = new DelegateCommand(OnApplyFilterCommand);
}
public ICollectionView ChildCollection
{
get { return data.View; }
}
public DelegateCommand ApplyFilterCommand { get; set; }
private void OnApplyFilterCommand()
{
data.View.Filter = new Predicate<object>(x => ((Child)x).Name == "Child 1");
OnPropertyChanged("ChildCollection");
}
}
//Sample Model used
public class Model
{
public Model()
{
ChildList = new HashSet<Child>();
}
public ICollection<Child> ChildList { get; set; }
}
public class Child
{
public string Name { get; set; }
}
//View
<ListBox ItemsSource="{Binding Path = ChildCollection}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Command="{Binding ApplyFilterCommand}"/>
A CollectionView is not always the best solution. you can also filter your collection using some simple LinQ. Take this simple example:
public ObservableCollection<TraceDataItem> FilteredData
{
get
{
return new ObservableCollection<TraceDataItem>(YourUnfilteredCollection.Where(
i => MeetsFilterRequirements(i)));
}
}
private bool MeetsFilterRequirements(TraceDataItem item)
{
return item.SomeProperty == someValue || item is SomeType;
}
The beauty of this method is that you can add some complex filtering requirements. One thing to note: whenever any properties in this method are changed, you'd need to call NotifyPropertyChanged("FilteredData") to ensure that the UI will be updated accordingly.

Categories

Resources