I have a list box that has a list of user controls. Each user control has 5 combo boxes. I want to be able to read the selected text of each combo box in each user control from the main application. However, when I change the selection in the combo box, the text property of the combo box in the user control doesn't change when I read it in the main application.
Code-behind:
radQueryParamList.Items.Add(new TCardQueryParameters());
Xaml (This is just a data template for how to display a TCardQueryParameters object):
<DataTemplate x:Key="TCardViewQueryParamDataTemplate">
<tcardqueryparam:TCardQueryParameters x:Name="TCardViewerParamUC" />
</DataTemplate>
<telerik:RadListBox Grid.Column="1" ItemTemplate="{StaticResource TCardViewQueryParamDataTemplate}" Name="radQueryParamList" VerticalAlignment="Top" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.ColumnSpan="3">
Where I loop over the list of user controls
string test = radTESTGACC.Text;//TEST combo box, Text property changes
//radQueryParamList is a listbox of user controls where TCardQueryParameters is the UC
foreach(TCardQueryParameters param in radQueryParamList.Items)
{
//Each UC has a radGACC combo box in it, and I am reading what the user
//selected for each user control here in the main app, but the text property
//never changes
String gacc = param.radGACC.Text; //Text property DOESN'T CHANGE
}
I thought that each instance of the user control would keep its own state and I would just be able to read what the user selected for that combo box, but that doesn't seem to be the case.
You have not bound the SelectedItem, SelectedValue, or SelectedIndex property of your internal ComboBox to anything so it maintains its selection.
An ItemTemplate is like a cookie cutter. It contains the definition of the object, but not the object itself. Properties specific to the object's state are lost unless they are bound to something on the DataContext behind the template.
This is important to note for two aspects.
First off, to improve performance WPF usually unloads items which are not visible, which often results in items being re-created from their template anytime they are reloaded. An example would be when you minimize an application to the taskbar, then maximize it again. This is usually better on performance and memory usage, however it does mean you have to be sure you store the state of items that were created with a Template somewhere.
And second, by default ListBoxes use something called virtualization. A simple way of explaining this would be this:
Suppose you have a ListBox of 100,000 items. In your ListBox, only 10 items can be visible at a time. WPF will render roughly 14 items (the 10 visible ones, and then a few extra for a scroll buffer so you don't see anything unusual while scrolling). When you scroll to new items, WPF just re-uses the existing items that are already rendered, and just replaces the DataContext behind those items.
As you can guess, it is far better on performance to render 14 UI items instead of 100k items.
So to answer your question, you will probably want to bind either SelectedItem, SelectedValue, or SelectedIndex of your TCardQueryParameters UserControl to a property on the DataContext (which in your case appears to be another different UserControl).
It should probably be noted that what you are essentially doing is creating a list of UserControls, assigning them to the ListBox, and then telling the ListBox that it should draw each UserControl with another separate UserControl. So although you are changing the SelectedItem in the template UserControl, that change is not being reflected to your ListBox.Items copy of the UserControl.
I'm guessing you probably don't want that, so would recommend removing your ItemTemplate completely.
Or better yet, create a new class object containing all the data for your UserControl, and adding that to your ListBox.Items. Then tell the ListBox to draw that item using a TCardQueryParameters UserControl as the ItemTemplate, like you have now.
Related
I have a ComboBox which uses ListBox to show items. ListBox’s ItemSource is bound with a CollectionViewSource.
Issue: Once I open ComboBox and scroll through the items and leave it in middle or at bottom. Once I reopen ComboBox, even though I refresh or reload ItemSource (CollectionViewSource), the Scrollbar remains at the same place where I left it last time. I want it to be as default (at top) each and every time I reload ItemSource.
Is there is any way of doing this in XAML itself? Another thing, I cannot use Behavior or Attach property. I want any template or style for this.
In order for a Style on a ListBox to embody some behavior for the <ScrollViewer>, you would need to use an Attached Property / Attached Behavior to control the ScrollViewer's "grabber" position. This is because your collection is bound to your ListBox and notifying when it is updated needs to drive a behavior that isn't natively on the ListBox. It may be possible to reset the scroll position with a <ControlTemplate> for the <ScrollViewer> itself, but I imagine it would be difficult as it would likely involve manipulating Transforms / StoryBoards based on DataTriggering with your ItemsSource, but again that may cause a dependency on needing to use an Attached property, which for some reason you can't use...
If you simply want to get a result now, and you don't care about testability or re-usability, I would handle the TargetUpdated event in the code-behind. It's ultimately what the Attached Behavior would end up doing. On the other hand, if you do care about re-usability then you need to evaluate and challenge why you can't use an Attached Behavior (they are also testable, too); an Attached Behavior would be also easier than trying to edit a ControlTemplate.
Here is the code-behind approach:
.xaml:
<ListBox x:Name="myListBox"
ItemsSource="{Binding MyItemsSource, NotifyOnTargetUpdated=True}"
TargetUpdated="ListBox_TargetUpdated"/>
.xaml.cs:
private void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
{
if (myListBox.Items.Count > 0)
myListBox.ScrollIntoView(myListBox.Items[0]);
}
Edit: On the flip-side, if you are using MVVM, you can do something like this SO post suggests and set IsSynchronizationWithCurrentItem="True" and when you refresh your ItemsSource, simply set your SelectedItem to the first in the list and handle the SelectionChanged event in your vm.
PersonsViewModel has a corresponding DataTemplate with a DataGrid bound to PersonList. DataGrid.SelectedItem has two-way databinding to SelectedPerson, so that when I select a row in the view, the corresponding item from PersonList is assigned to SelectedPerson.
It works fine except for one problem: when I switch screens, say, to PersonDetailScreen, and come back, the selection focus is lost! I mean, SelectedPerson still contains its former value, but the DataGrid comes out visually unselected.
I made a test, creating a two-way databound "SelectedIndex" in viewmodel. Then, I can see the actual selection is still present in viewmodel when it comes back, the problem seems to be:
"How to focus the SelectedIndex of an ItemsControl when ViewModel's datatemplate is loaded and some "SelectedIndex" databound property in such viewmodel already contains a value?"
If you have a TwoWay Binding then you can set the DataGrid.SelectedItem value from your view model. I'm a little bit confused as to your set up though... when you say 'when I switch screens ... and come back, the selection focus is lost', it sounds a bit like you're keeping the view model alive, but not the view? I'm more used to displaying a fresh view each time, but the fix would be the same either way.
Now if this were one of my views, I'd load the data into any collections from the constructor and (internally in a base class) set the CurrentItem property that is data bound to the ListBox.SelectedItem property to the first item in the collection. You should do the same, except that instead of setting it to the first item, you'd set it to whichever item was last selected.
So the answer is just to set the SelectedItem property each time the view is displayed. However, if you're saying that the SelectedItem property is already set to the item that should be selected, you may need to set it to any other value first and then set it back to the correct item.
What are we talking about here, item selection or item focus?
If you want a visual item to get focus when a template is rendered, your best bet is to set the focus manually in your xaml's code behind, by, say, hooking a handler to your page's 'Loaded' event and setting the focus manually to your datagrid by calling on its 'Focus()' method.
I know this breaks some MVVM rules, but focus management is highly dependent on your page's visual tree, and thus cannot be properly modeled through a viewmodel, which should be as view-independent as possible.
Thanks to Sheridan's insights, I have solved the problem easier than I imagined, and somewhat unintentionally.
I had the DataGrid defined directly in a DataTemplate. When the template loaded, I BELIEVE, although I am not sure at all, that some initialization step required to pass the "SelectedItem" or "SelectedItem" value to the View was "lost".
I planned to do this re-selection manually in code behind, so what I did was to move the datagrid to some UserControl I created from scratch. But then, the very fact that the DataTemplate now instantiates a proper View (derived from UserControl) inside itself, which in turn contains the datagrid, seems to "make more notifications work", so to say, and the View displays selected row like it always should.
So, now I have this in my DataTemplate:
<DataTemplate x:Name="PersonScreenTemplate" DataType="{x:Type vm:PersonScreenViewModel}">
<vw:PersonScreenViewView/>
</DataTemplate>
That seems to be the perfect pure-WPF design pattern for ViewModel-first: a datatemplate whose content is a single usercontrol, which in turn declares and binds and handles everything.
I have a WPF Data Grid bound to an observable collection, which is working as intended.
What I am trying to do now is add text below it to say: "Number of selected rows: {count goes here}"
What's the proper way to do this? I could add a new property in the View Model called SelectedCount or something similar and bind to that, but it doesn't feel right. It seems redundant. Also, I could set the label text dynamically in the code behind, but I'm not sure if that's the "right" place to do this either.
Here's an example below.
EDIT:
Please pretend there's a checkbox column header whose intention is to provide check/uncheck all functionality. The state of this header checkbox should not count towards the final count.
You could use element binding to declaratively bind to the SelectedItems.Count property in XAML:
<TextBlock Text="{Binding ElementName=MyDataGrid,
Path=SelectedItems.Count, StringFormat=Number of selected rows: {0}}" />
Update
Presumably you're using MVVM, so adding a SelectedXCount property to your view model is a perfectly reasonable application of the view model. The advantage of having it in the view model is that you could unit test based on the number of selected items. E.g. if you want to check that the user can only progress (a CanNext property returns true) if the user has selected some items.
The SelectedItems property is not a DependencyProperty so can't be bound to, but there are many articles online that get around the issue when using the DataGrid in MVVM. Most of the solutions involve using a mechanism for calling a view model command on the invocation of the DataGrid's SelectionChanged event.
I am looking for a way where a control can be enable when an item from a combo box is selected. Is there a simple way through data binding when a user selects an item from a combo box that it then enables another control to be used?
If you're using MVVM, you can bind the SelectedItem of the combobox to a property in your viewmodel.
Say this is your combobox:
<ComboBox ItemsSource="{Binding widgetlist}" SelectedItem="{Binding Path=selectedwidget, Mode=TwoWay}"></ComboBox>
And this is your control:
<DockPanel IsEnabled="{Binding controlenabled}">
...
</DockPanel>
Then in selectedwidget's setter, you can change the controlenabled property to False or True. Don't forget to notify that the controlenabled property changed (or if you want, make controlenabled a DependencyProperty.)
In summary, you've got 3 properties to bind to:
widgetlist, an ObservableCollection or some other collection that is the source for your combobox
selectedwidget, an item of that collection type that changes to whatever the combobox currently has selected
controlenabled, a bool that the other controls look at to decide if they are enabled/disabled.
Like many examples in MVVM, this way may require slightly more thought and code on the outset, but will be far more maintainable and scalable later. For example, say you want some more controls to also enable/disable themselves based on the same scenario. Piece of cake: add IsEnabled="{Binding controlenabled}"> to them.
Yes. You want to bind to IsEnabled in the target control which you want to dynamically enable or disable, and use a Value Converter to convert a matching string or item from the ComboBox to a true value for being enabled.
Ok, hopefully this is simple but for some reason I can't find a straight answer and I'm not familiar enough with WPF yet to know how to do it.
I have a listview, it gets bound to an observable collection of objects to display. I want to have a context menu with a bunch of options. The options in the context menu are relative to the particular object in the list that was clicked on (things like delete, export, etc).
So I need the object that the user right clicked on in my listview to be passed as a parameter to the command that the context menu executes.
How do I do this?
Edit: I should mention I would prefer a solution that is mostly (if not entirely) xaml - I'm trying to avoid having significant code in the code-behind. If that's the only way to do it though...
Further Edit: More details that I forgot to mention that are important. The command I want executed is on the object bound to the data context of my user control, it is not on the objects in the list view. So I need the context menu's on the list view's items to be bound to a command that is on the user control's data context, and the listview item passed as a parameter into that command.
It depends on whether your ContextMenu is part of the template for individual items, or if it is attached to the ListBox as a whole.
If you are attaching your ContextMenu to the items in the list using a DataTemplate (this is generally the best way to do it), the DataContext on the MenuItem is already set so all you need to do is:
<MenuItem ... CommandParameter="{Binding}" />
On the other hand, if your ContextMenu is attached to the ListBox as a whole, you'll need to access the SelectedItem property of the ListBox:
<MenuItem ... CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor,ListBox,1}} />