I have a list of items with id and description(i can introduce key-value collection instead if needed). What i need is control that binded to viewmodel id property, but shows description of corresponding item/pair on it. Closest example i know is combobox, where i set DisplayMemberPath and SelectedValue/SelectedValuePath, but i don't need dropdown. So is there any in-built control in Silverlight for this?
(of course i can code one myself, it's easy and I can even just put some logic for viewmodel to get pair i need and bind it's description to simple textblock)
Edit: To illustrate what funcionality i need i coded simple example class. It actually satisfies my needs, but i still want to know if i can use built-in control.
public class CollectionItemDisplayControl:TextBox
{
public CollectionItemDisplayControl()
{
IsReadOnly = true;
}
public string SelectedID
{
get { return (string)GetValue(SelectedIDProperty); }
set { SetValue(SelectedIDProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedID. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedIDProperty =
DependencyProperty.Register("SelectedID", typeof(string), typeof(CollectionItemDisplayControl), new PropertyMetadata(new PropertyChangedCallback(OnSelectedIDChangedStatic)));
private static void OnSelectedIDChangedStatic(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CollectionItemDisplayControl originator = d as CollectionItemDisplayControl;
if (originator != null)
{
originator.OnSelectedIDChanged(e);
}
}
private void OnSelectedIDChanged(DependencyPropertyChangedEventArgs e)
{
string description = String.Empty;
string value = e.NewValue as string;
if (value != null)
{
foreach (var item in _items)
{
if (item.UniqueID == value)
{
description = item.Description;
break;
}
}
}
Text = description;
}
private IDataCollection _viewModel;
public IDataCollection ViewModel
{
get { return _viewModel; }
set
{
_viewModel = value;
if (_viewModel != null)
{
_items = _viewModel.Items;
}
}
}
private ObservableCollection<IUnique> _items = new ObservableCollection<IUnique>();
}
ItemClass contains two properties: ID and Description. I can place this control on the page, bind Items, and one-way bind SelectedID.
Edit 2: well i didn't make SelectedID DependencyProperty so binding won't work, but i will fix it right away
Edit 3: first snippet was sloppy and didn't work properly, so i fixed it.
If I understood properly,
You just need the right binding implemented.
(you do need a list? not just a single item, even if single it's similar just any control)
Bind the list to e.g. ItemsControl.
Set ItemsSource to your list of items
Then override ToString on your Item providing it's 'yours' really. If not you can make your own wrapper.
Within ToString output whatever is presenting your item, e.g. description.
That's a quickest solution, you can also make item template as you want.
EDIT:
well just put everything in the view model and bind to it - the TextBox, i.e.
Text={Binding SelectedText}
e.g.
...in your view model add SelectedText and SelectedID (and Items if needed) - properly do OnPropertyChanged.
Set SelectedID from view model or if 'bound' from another control that may change it.
Within set for SelectedID set the SelectedText.
No need for a control for things like that, it's all data binding really.
Related
I have a user control with a dependency property:
public ObservableCollection<Exclusion> SelectedExclusions
{
get
{
return (ObservableCollection<Exclusion>)GetValue(SelectedExclusionsProperty);
}
set
{
SetValue(SelectedExclusionsProperty, value);
}
}
public static readonly DependencyProperty SelectedExclusionsProperty =
DependencyProperty.Register(nameof(TimeSeriesChart.SelectedExclusions),
typeof(ObservableCollection<Exclusion>),
typeof(TimeSeriesChart),
new PropertyMetadata(default(ObservableCollection<Exclusion>)));
I am adding a selected exclusion to this collection on key down:
protected override void OnKeyDown(KeyEventArgs e)
{
if(e.Key == Key.Delete)
{
this.SelectedExclusions.Add(this.ExclusionProviders[0].Exclusions[this.hitTestInfo.DataSeriesIndex]);
}
}
In the view model I have this property & backing variable:
private ObservableCollection<TimeSeriesLibraryInterop.Exclusion> selectedExclusionsToDelete = new ObservableCollection<TimeSeriesLibraryInterop.Exclusion>();
public ObservableCollection<TimeSeriesLibraryInterop.Exclusion> SelectedExclusionsToDelete
{
get
{
return this.selectedExclusionsToDelete;
}
set
{
this.selectedExclusionsToDelete = value;
this.RaisePropertyChanged();
}
}
Finally the binding in the view:
<userControl1 SelectedExclusions="{Binding SelectedExclusionsToDelete, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The dependency property collection is initialised and populated however the view model property setter is never hit when the dependency property collection changes (Add). I have no binding errors in the output window. Is there something I'm missing here?
Looks like you're adding an item to the collection rather than replacing the collection. You won't hit the vm collection property's setter that way.
If you want to your viewmodel to respond to items being added to the SelectedExclusionsToDelete collection, the viewmodel will need to handle the SelectedExclusionsToDelete.CollectionChanged event. "Properly" handling that event (remove, add, move, clear, etc.) is a real hassle, but if it's not a giant collection you can often get away with something quick and dirty: Treat any change as a whole new collection. I think that's exactly the case you've got, too.
Alternatively, for an even quicker and dirtier approach, I think you could make it a two-way binding by default and have the control assign a new ObservableCollection to this.SelectedExclusions in OnKeyDown. The binding will pass it back to the viewmodel and hit the setter.
I have a method that queries a database using entity framework and places the results in an ICollectionView. The ICollectionView acts as the ItemsSource for a DataGrid. Everything works fine on the first query, but upon querying a second time, the data is not properly sorted, despite the application of the correct SortDescriptions.
Here is my code for trying querying and grouping/sorting the data:
CollectionViewSource cvsRS;
private ObservableCollection<productorder> rs;
public ObservableCollection<productorder> RS
{
get { return rs; }
set
{
if (rs != value)
{
rs = value;
OnPropertyChanged("RS");
}
}
}
private ICollectionView rsView;
public ICollectionView RSView
{
get { return rsView; }
set
{
if (rsView != value)
{
rsView = value;
OnPropertyChanged("RSView");
}
}
}
public void QueryDatabase()
{
RS = new ObservableCollection<productorder>(DatabaseEntities.productorders.Where(o => o.month.id == CurrentMonth.id));
if (RS != null)
{
cvsRS.Source = RS;
RSView = cvsRS.View;
RSView.GroupDescriptions.Clear();
RSView.GroupDescriptions.Add(new PropertyGroupDescription("producttype.productcategory.name"));
RSView.GroupDescriptions.Add(new PropertyGroupDescription("producttype.name"));
RSView.SortDescriptions.Clear();
RSView.SortDescriptions.Add(new SortDescription("producttype.productcategory.sortorder", ListSortDirection.Ascending));
RSView.SortDescriptions.Add(new SortDescription("client.name", ListSortDirection.Ascending));
RSView.Refresh();
CurrentRecord = null;
SelectedRecords = null;
}
}
The grouping works fine, but the groups aren't in the correct order based on the sorting. I've tried a number of possible "fixes" with no success (e.g. adding sort/group descriptions directly to the CollectionViewSource, sorting before grouping, removing some of the sorting/grouping, removing the SortDescriptions per CollectionViewSource does not re-sort on property change).
Does anyone know how to maintain the sort order regardless of how many queries are performed? I'm open to alternative methods of querying displaying the data in the DataGrid if that may work.
Try binding your CollectionViewSource.Source property to your ObservableCollection<T> property. Set up the binding in the viewmodel constructor. Then, just leave it alone. Update the ObservableCollection<T>, replace it, etc. As long as it's an ObservableCollection<T> and its public property raises PropertyChanged whenever you replace it, the whole thing will work.
public MyViewModel()
{
BindCollectionViewSource();
}
protected void BindCollectionViewSource()
{
cvsRS = new CollectionViewSource();
var binding = new Binding
{
Source = this,
Path = new PropertyPath("RS")
};
BindingOperations.SetBinding(cvsRS, CollectionViewSource.SourceProperty, binding);
}
// Since we're not going to be messing with cvsRS or cvsRS.View after the
// constructor finishes, RSView can just be a plain getter. The value it returns
// will never change.
public ICollectionView RSView
{
get { return cvsRS.View; }
}
You can't just assign a binding to Source; there's more to it than that. The Source="{Binding RSView}" stuff you see in XAML may look like an assignment, but some details are being hidden for convenience. The Binding actively does stuff. It needs to know who the target object is.
I did see one funny thing: I gave my test code one PropertyGroupDescription and one SortDescription. When I added items to the collection, it sorted them within the groups. Then when I called RSView.Refresh(), it resorted them without reference to the groups. Not sure I understood what it was doing there.
I'm building an MVVM Light WPF app in Visual Studio 2015 with Entity Framework 6 (EF) providing the data. I have a ComboBox that displays the reasons why someone needs to take a drug test and it looks like this:
<ComboBox ItemsSource="{Binding ReasonsForTest}"
SelectedItem="{Binding Path=ReasonsForTestVm,
UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Description" />
The ReasonsForTest is of type ReasonForTestViewModel class:
public class ReasonForTestViewModel: ViewModelBase
{
private int _ReasonForTestId;
private string _ReasonForTestAbbr;
private string _description;
public int ReasonForTestId
{
get { return _ReasonForTestId; }
set
{
if (value == _ReasonForTestId) return;
_ReasonForTestId = value;
RaisePropertyChanged();
}
}
public string ReasonForTestAbbr
{
get { return _ReasonForTestAbbr; }
set
{
if (value == _ReasonForTestAbbr) return;
_ReasonForTestAbbr = value;
RaisePropertyChanged();
}
}
public string Description
{
get { return _description; }
set
{
if (value == _description) return;
_description = value;
RaisePropertyChanged();
}
}
}
I have a data service class that contains the following code to fetch the data for the valid values of the ComboBox:
public async Task<ObservableCollection<ReasonForTestViewModel>> GetReasonsForTest()
{
using (var context = new MyEntities())
{
var query = new ObservableCollection<ReasonForTestViewModel>
(from rt in context.ReasonForTests
orderby rt.description
select new ReasonForTestViewModel
{
ReasonForTestId = rt.ReasonForTestID,
ReasonForTestAbbr = rt.ReasonForTestAbbr,
Description = rt.description,
});
return await Task.Run(() => query);
}
}
The view model populates the ComboBox using this:
var dataService = new TestDataService();
ReasonsForTest = await dataService.GetReasonsForTest();
The ComboBox has the correct data; however, it's not selecting the correct value when the app starts -- it's showing blank on load. The SelectedItem (ReasonsForTestVm) is also of that class type ReasonForTestViewModel and gets populated from the database with the one item for this person. I've stepped through the code to ensure ReasonsForTestVm has the correct data, and it does.
Here's the property for ReasonsForTestVm:
public ReasonForTestViewModel ReasonForTestVm
{
get
{
return _reasonForTestVm;
}
set
{
if (Equals(value, _reasonForTestVm)) return;
_reasonForTestVm = value;
RaisePropertyChanged();
}
}
What am I doing wrong here? I'm about to lose my mind!
Update: Sorry for the confusing name in the property above. Fixed.
Any WPF items control that extends Selector (such as ComboBox and ListBox) has two properties that are often used in conjunction: ItemsSource and SelectedItem.
When you bind a collection to ItemsSource, a representation of those items are shown in the UI. Each one of the representations is bound to an instance found within the collection bound to ItemsSource. If, for an example, you're using a DataTemplate to create that representation, you'll find within each that the DataContext will be one of those instances from the collection.
When you select one of these representations, the SelectedItemproperty now holds the instance from the collection that was bound to that representation.
This works perfectly through user interaction with the UI. However, there's one important caveat when interacting with these controls programmatically.
It's a very common pattern to bind these properties to similar properties in your view model.
public class MuhViewModel
{
public MuhItems[] MuhItems {get;} = new[]{ new Item(1), new Item(2) };
// I don't want to show INPC impls in my sample code, kthx
[SuperSlickImplementINotifyPropertyChangedAttribute]
public MuhSelectedItem {get;set;}
}
bound to
<ComboBox ItemsSource="{Binding MuhItems}"
SelectedItem="{Binding MuhSelectedItem}" />
If you try to manually update the selected item this way...
muhViewModel.MuhSelectedItem = new Item(2);
The UI will not change. The Selector sees that ItemsSource has changed, yes, but it doesn't find that instance in the ItemsSource collection. It doesn't know that one instance of Item with a value of 2 is equivalent to any other Item with the same value. So it does nothing. (That's a bit simplistic for what really happens. You can bust out JustDecompile and see for yourself. It gets real convoluted down in there.)
What you should be doing in this situation is updating SelectedItem with an instance found within the collection bound to ItemsSource. In our example,
var derp = muhViewModel.MuhItems.FirstOrDefault(x => x.MuhValue == 2);
muhViewModel.MuhSelectedItem = derp;
Side note, when tracking instances within a debug session, it helps to use Visual Studio's Make Object ID feature.
I have a MVVM Windows Phone 8 app. The XAML page has a user control that I created that needs to be notified when a change takes place in the View Model. To facilitate this, I created an int property in the user control to be bound to a property in the View Model, so the user control property's setter method would be triggered when the property it was bound to in the View Model changed.
Using the code below, the user control's VideosShownCount property does show up in the Property List at design-time but when I click on the binding mini-button, the Create Data Binding option is greyed out in the pop-up menu.
So I have one or two questions, depending on what is the root problem:
1) How do I make a property in a View Model available as a Data Binding source?
2) How do I format a user control property so the IDE allows it to be data bound to a View Model property?
private int _videosShownCount = 0;
public int VideosShownCount
{
get
{
return this._videosShownCount;
}
set
{
this._videosShownCount = value;
}
}
public static readonly DependencyProperty VideoShownCountProperty =
DependencyProperty.Register("VideosShownCount", typeof(int), typeof(MyUserControl),
new PropertyMetadata(0, new PropertyChangedCallback(VideoShownCountPropertyChanged)));
static void VideoShownCountPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MyUserControl MyUserUserControl = (MyUserControl)sender;
// Don't care about the value, just want the notification.
// int val = (int)e.NewValue;
// Do work now that we've been notified of a change.
MyUserUserControl.DoWork();
}
You're not using the DependencyProperty for your property, which will definitely cause problems between your code and the bindings
public int VideosShownCount
{
get { return (int) GetValue(VideosShownCountProperty); }
set { SetValue(VideosShownCountProperty, value); }
}
I'm not sure if this is the main cause of your problem, but it's worth fixing regardless.
About Data Virtualizatoin in WPF, the WPF: Data Virtualization is a good article.
With using this, Data Virtualization was executed as good in my code but there is the one problem, which is that I cannot bind a property in ViewModel with SelectedItem of ItemsControl in View. If one item of data satisfies some condition while data loads, the one item will be set as a property in ViewModel and then it will be bound with SelectedItem of ItemsControl in View, but will not.
My code about this is the following. About the types of IItemsProvider andVirtualizingCollection, please refer to the WPF: Data Virtualization.
So far, I have tried:
I'm sure that if Data Virtualization were not used, the Selected Item Binding would be cool.
The IndexOf(T item) method in VirtualizingCollection returns always -1. As thinking this would be the problem, I implemented that the IndexOf(T item) returns a actual index, but it was not concerned with this problem.
The code of implementing IItemsProvider
public class WordViewModelProvider : IItemsProvider<WordViewModel>
{
private string _searchText = "some text";
public WordViewModel SelectedItem
{
get;
private set;
}
#region IItemsProvider<WordViewModel> Members
public int FetchCount()
{
lock (_words)
{
int count = (from word in _words
where word.Name.Contains(_searchText)
select word).Count();
return count;
}
}
public IList<WordViewModel> FetchRange(int startIndex, int count)
{
lock (_words)
{
//Please, regard _word as IEnumerable<Word>
IQueryable<Word> query = (from word in _words
where word.Name.Contains(_searchText)
select word);
List<WordViewModel> result = query.ToList().ConvertAll(w =>
{
var wordViewModel = new WordViewModel(w, _searchText);
if (w.Name.Equals(_searchText, StringComparison.InvariantCultureIgnoreCase))
{
SelectedItem = wordViewModel;
}
return wordViewModel;
});
return result;
}
}
#endregion
}
The code of using VirtualizingCollection in ViewModel
public void ViewList()
{
var wordViewModelProvider = new WordViewModelProvider();
var virtualizingCollection = new VirtualizingCollection<WordViewModel>(wordViewModelProvider);
//IList<WordViewModel> type to bind with View's ItemsSource.
WordViewModels = virtualizingCollection;
//WordViewModel type to bind with View's SelectedItem
SelectedItem = wordViewModelProvider.SelectedItem;
}
I would like to post good references about Virtualization to deal with large data set in WPF.
UI Virtualization vs Data Virtualization.
For Virtualization approaches:
Paul McClean
Vincent Van Den Berghe
bea.stollnitz: He/She describes the solution that combines some of the best features of the two former and covers my issue.