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.
Related
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
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;
Ok, so I normally wait until the last possible moment to submit a question but I could really use some help.
I'm attempting to bind an ObservableCollection<Contact_Info> to a WPF TreeView Control. Contact_Info contains properties for contact information and implements INotifyPropertyChanged. It also contains a list of more detailed information for the given Contact.
I'm having trouble with 2 things. 1) I need to display on the TreeViewItem header the name of the Company however, a lot of the Contacts have a missing Company Name in which, I want to use the First and Last name. I have attempted to use things like FallBackValue and TargetNullValue but the CompanyName is not null, its just empty so I can't seem to get that right. Do I need a converter? 2) I cannot seem to get the hierarchy correct in that, I want all Contacts of the same AccountID to be grouped together within a TreeViewItem. Currently, I can only get the Company Name to display on the TreeViewItem and expanding it displays a blank value and there are still duplicate Company Names displayed..
Code for my attempt at a HierarchicalDataTemplate placed under the DockPanel.Resources (not sure if that's the right place either).
<local:Main_Interface x:Key="MyList" />
<HierarchicalDataTemplate DataType = "{x:Type
local:Contact_Info}" ItemsSource = "{Binding Path=OppDataList}">
<TextBlock Text="{Binding Path=CompanyName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType = "{x:Type sharpspring:SharpspringOpportunityDataModel}" >
<TextBlock Text="{Binding Path=ProjectName}"/>
</DataTemplate>
The above code is the most recent thing I have tried among countless other ways. I feel that I'm really close but at this point I could really use some help from SO..
The other classes are a little long so let me know if you need more. Any help provided is greatly appreciated!
Edit 1: Contact_Info
public class Contact_Info : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
internal SharpspringLeadDataModel LeadDataModel;
internal SharpspringOpportunityDataModel OppData;
public List<SharpspringOpportunityDataModel> OppDataList { get; set; }// Should i make this ObservableCollection?
public Project_Info ProjectInfo { get; set; }
public bool IsDirty { get; set; }
public bool IsNew { get; set; }
#region Properties
public long AccountID
{
get => LeadDataModel.AccountID;
set
{
if (value != LeadDataModel.AccountID)
{
LeadDataModel.AccountID = value;
NotifyPropertyChanged();
}
}
}
public long LeadID
{
get => LeadDataModel.LeadID;
set
{
if (value != LeadDataModel.LeadID)
{
LeadDataModel.LeadID = value;
NotifyPropertyChanged();
}
}
}
// Lead Info
public string CompanyName
{
get => LeadDataModel.CompanyName;
set
{
if (value != LeadDataModel.CompanyName)
{
LeadDataModel.FaxNumber = value;
NotifyPropertyChanged();
}
}
}
public Contact_Info(SharpspringLeadDataModel lead, SharpspringOpportunityDataModel opp)
{
LeadDataModel = lead;
OppData = opp;
ProjectInfo = new Project_Info(this);
}
public SharpspringLeadDataModel GetDataModel()
{
return LeadDataModel;
}
public SharpspringOpportunityDataModel GetOppModel()
{
return OppData;
}
public Project_Info GetProjectInfoModel()
{
return ProjectInfo;
}
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
IsDirty = true;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Edit 2: Currently populates tree but does not group Companies together...
private void PopulateTreeView(List<SharpspringLeadDataModel> lead_list, List<SharpspringOpportunityDataModel> opp_list)
{
Contacts = new ObservableCollection<Contact_Info>();
var complete2 =
from lead in lead_list
let oppList = from o in opp_list
where o.PrimaryLeadID == lead.LeadID
select o
select new Contact_Info()
{
LeadDataModel = lead,
OppData = oppList.DefaultIfEmpty(new SharpspringOpportunityDataModel()).First(),
OppDataList = oppList.ToList(),
};
Contacts = complete2.ToObservableCollection();
Lead_Treeview.ItemsSource = Contacts;
}
I think your problem is really with using List instead of ObservableCollection for OppDataList. Using your code (reduced to minimal needed) but with the ObservableCollection driving the collection it works fine for me.
Note that my code will work fine with List even because its all static but in a proper app you'll need OC to track changes in collection.
Disclaimer: I'm using code behind to answer this question to keep it simple but I do not recommend any such use in any real app
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
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"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="240" Width="320">
<TreeView ItemsSource="{Binding Contacts}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Contact_Info}" ItemsSource="{Binding Path=OppDataList}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type local:SharpspringOpportunityDataModel}">
<TextBlock Text="{Binding Path=ProjectName}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Path=CompanyName}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Window>
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using WpfApp1.Annotations;
namespace WpfApp1
{
public partial class MainWindow : INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public ObservableCollection<Contact_Info> Contacts { get; } =
new ObservableCollection<Contact_Info>
{
new Contact_Info
{
CompanyName = "Apple",
OppDataList = {new SharpspringOpportunityDataModel {ProjectName = "World take over"}}
},
new Contact_Info
{
CompanyName = "Google",
OppDataList = {new SharpspringOpportunityDataModel {ProjectName = "World take over"}}
},
new Contact_Info
{
CompanyName = "Microsoft",
OppDataList = {new SharpspringOpportunityDataModel {ProjectName = "World take over"}}
}
};
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Contact_Info
{
public string CompanyName { get; set; }
public ObservableCollection<SharpspringOpportunityDataModel> OppDataList { get; } =
new ObservableCollection<SharpspringOpportunityDataModel>();
}
public class SharpspringOpportunityDataModel
{
public string ProjectName { get; set; }
}
}
Screenshot
based on my understanding your OppDataList is the sub property for the given Contract info and you want it to be collapsible.
In such case you can do
<ListBox ItemsSource="{Binding YourContactList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding ProjectName}">
<Expander Header="{Binding CompanyName}">
<ListBox ItemsSource="{Binding OppDataList}">
<!--Your OppDataList display-->
</ListBox>
</Expander>
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
End result will look similar to this
+Contract1
-Contract2
-Project2
-Company2
-Opp
1
2
3
+Contract3
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.
I've figured out a way to bind user controls inside a tree view dynamically with ReactiveUI.
But ...
The top level binding to the HierachicalDataSource is in the XAML not the code behind, and I need to set the ItemsSource directly and not use this.OneWayBind per the general pattern for ReactiveUI binding.
So, my question is: did I miss something in the ReactiveUI framework that would let me bind with this.OneWayBind and move the HierachicalDataTemplete into the code behind or a custom user control?
In particular- Is there another overload of OneWayBind supporting Hierarchical Data Templates, or a way to suppress the data template generation for the call when using it?
Update
I've added selected item, and programatic support for Expand and Selected to my test project, but I had to add a style to the XAML. I'd like to replace that with a simple RxUI Bind as well. Updated the examples.
Here are the key details:
Tree Control in Main View
<TreeView Name="FamilyTree" >
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
<HierarchicalDataTemplate DataType="{x:Type local:TreeItem}" ItemsSource="{Binding Children}">
<reactiveUi:ViewModelViewHost ViewModel="{Binding ViewModel}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
main view code behind
public partial class MainWindow : Window, IViewFor<MainVM>
{
public MainWindow()
{
InitializeComponent();
//build viewmodel
ViewModel = new MainVM();
//Register views
Locator.CurrentMutable.Register(() => new PersonView(), typeof(IViewFor<Person>));
Locator.CurrentMutable.Register(() => new PetView(), typeof(IViewFor<Pet>));
//NB. ! Do not use 'this.OneWayBind ... ' for the top level binding to the tree view
//this.OneWayBind(ViewModel, vm => vm.Family, v => v.FamilyTree.ItemsSource);
FamilyTree.ItemsSource = ViewModel.Family;
}
...
}
MainViewModel
public class MainVM : ReactiveObject
{
public MainVM()
{
var bobbyJoe = new Person("Bobby Joe", new[] { new Pet("Fluffy") });
var bob = new Person("Bob", new[] { bobbyJoe });
var littleJoe = new Person("Little Joe");
var joe = new Person("Joe", new[] { littleJoe });
Family = new ReactiveList<TreeItem> { bob, joe };
_addPerson = ReactiveCommand.Create();
_addPerson.Subscribe(_ =>
{
if (SelectedItem == null) return;
var p = new Person(NewName);
SelectedItem.AddChild(p);
p.IsSelected = true;
p.ExpandPath();
});
}
public ReactiveList<TreeItem> Family { get; }
...
}
TreeItem base class
public abstract class TreeItem : ReactiveObject
{
private readonly Type _viewModelType;
bool _isExpanded;
public bool IsExpanded
{
get { return _isExpanded; }
set { this.RaiseAndSetIfChanged(ref _isExpanded, value); }
}
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set { this.RaiseAndSetIfChanged(ref _isSelected, value); }
}
private TreeItem _parent;
protected TreeItem(IEnumerable<TreeItem> children = null)
{
Children = new ReactiveList<TreeItem>();
if (children == null) return;
foreach (var child in children)
{
AddChild(child);
}
}
public abstract object ViewModel { get; }
public ReactiveList<TreeItem> Children { get; }
public void AddChild(TreeItem child)
{
child._parent = this;
Children.Add(child);
}
public void ExpandPath()
{
IsExpanded = true;
_parent?.ExpandPath();
}
public void CollapsePath()
{
IsExpanded = false;
_parent?.CollapsePath();
}
}
Person Class
public class Person : TreeItem
{
public string Name { get; set; }
public Person(string name, IEnumerable<TreeItem> children = null)
: base(children)
{
Name = name;
}
public override object ViewModel => this;
}
person view user control
<UserControl x:Class="TreeViewInheritedItem.PersonView"... >
<StackPanel>
<TextBlock Name="PersonName"/>
</StackPanel>
</UserControl>
Person view code behind
public partial class PersonView : UserControl, IViewFor<Person>
{
public PersonView()
{
InitializeComponent();
this.OneWayBind(ViewModel, vm => vm.Name, v => v.PersonName.Text);
}
...
}
Pet work the same as person.
And the full project is here ReactiveUI Tree view Sample
I reviewed the ReactiveUI source and this is the only way to do this. The Bind helper methods always use a DataTemplate and not a HierarchicalDataTemplate.
So this approach will use the XAML binding for the very top level and then let you use ReactiveUI binding on all of the TreeView items.
I'll see about creating a pull request to handle this edge case.
Thanks,
Chris