Refresh Scroll position of WPF ComboxBox on ItemSource's reload - wpf

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.

Related

Combobox Text property not changing in user control

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.

DataGrid not showing focus for SelectedItem when ViewModel is redisplayed

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.

TemplateBinding DataContext in silverlight CustomControl

I have a rather interesting case with ComboBox control - CustomComboBox;
In the style of this ComboBox, Popup contains one custom control that requests a DataContext;
<ctrl:CustomGrid DataContext="{TemplateBinding DataContext}" GridName="{Binding Preferences.CurrentGridName}"/>
The idea:
to use this control several times on one page
to use it in a masterpage container
the masterpage control needs to have different DataContexts regarding the Page it is on
The logic:
In the overriden OnApplyTemplate I am getting the grid and connecting few eventhandlers
The problem:
The masterpage control is triggering OnApplyTemplate only once
The first appearance of the CustomComboBox is as expected.
However, every next apearance is with same DataContext, even when changing the datacontext of the CustomComboBox
These changes don't reach to change my CustomGrid DataContext
I am sure that something on the bindings or the presentation logic is bad...
Please throw some thoughts on, I would appreciate a hint here
Thanks
OnApplyTemplate is called when a ControlTemplate is applied to the control that overrides the method (neither its parent, nor children). If OnApplyTemplate is entered once, the overriding control must also be created once. I mean you simply have a single masterpage instance. This shouldn't be unexpected.
Speaking about Popups and DataContext, there often are issues with bindings from a Popup to outside it. So, I would rather write some code-behind to deliver correct context to Popups, instead of relying on Bindings. There sure is a problem of DataContextChanged event absence prior to SL5. To workaround this one, you should define your custom DependencyProperty on your CustomComboBox, bind it to the CustomComboBox's context and assign its value to the Popup in the PropertyChangedCallback.

Setting a property on the ViewModel from the View in WPF

I have a dependency property on my ViewModel which is the DataContext for my View. The ViewModel has no reference to the View. The property on the ViewModel is going to reference a control on the view, but I need to be able to set this property in XAML.
How is this possible? One thought I had was to develop a custom control which has a Property property and a Value property, so you could do something like this in the View to set the property on the ViewModel:
<PropertySetter Property="{Binding MyViewModelDependencyProperty}" Value="{Binding ElementName=aControlOnMyView" />
Before I went down this route, I wanted to check if there was any other approach I could take?
Thanks for the detailed reply Ray, but if I give you a bit more detail about the problem I'm trying to solve, you might get a better idea of why I mentioned the approach I did.
Basically, what I'm trying to do is set the focus to a textbox when the user hits a button. I've written an attached property which you can attach to the Button control, specify what the trigger event is (in this case the 'Click' event), and then what control to focus on. This works really nicely, and keeps everything in XAML.
However, I now have a use case where the focus should be set to an arbitrary text box from the click event on a button which is part of a toolbar. This toolbar is itself a user control which is sitting inside another user control, which is inside another user control! This toolbar needs to be reusable across various different forms, and each time, the control to set focus on after you click the button will be different per form.
That's why I had the idea of making the focus control (i.e. a textbox) a property on the view model itself (on my ViewModel base to be precise), and have the ViewModel base code (which the toolbar is bound to), set the focus to the control when the button is clicked (and the e.g. Add/Edit method is called on the ViewModel base).
In unit test land, the control to focus on property will be null, so it's .Focus() method just won't be called. So I can't see an issue there. My problem is then how you set the focus control property from XAML, which is why I had the PropertySetter idea.
I don't like the fact that the ViewModel has any reference to controls sitting on the view, but I can't see another way to achieve what I need. What if the logic that dictates whether to set focus to the control is quite complex? This would sit in the ViewModel surely? Therefore, is there any harm in the ViewModel having this UIElement property? It still knows nothing about the specific View it is bound to, it just knows that there is a control which it needs to set focus to when some action happens on the ViewModel.
My first reaction (and it's a strong one) is so say "Don't do that!" By giving your view model a reference to a part of your UI you are breaking the encapsulation that makes view models so powerful and useful.
For example, what if you want to unit test your view model or serialize it to disk? In each case the piece of your UI will not be present, because there will be no view at all. Your tests will miss coverage and your reconstitution will be incomplete.
If your view model actually needs references to UI objects and there is no better way to architect it, the best solution is to have the view model itself construct those controls it requires a reference to. Then your view can incorporate that control as the Content of a ContentPresenter via binding and provide a Style to configure the control, including a ControlTemplate to provide its content. Thusly:
public class MyViewModel
{
public ListBox SpecialControl { get; set; }
public MyViewModel()
{
SpecialControl = new ListBox();
}
}
and
<DataTemplate TargetType="{x:Type local:MyViewModel}">
<DataTemplate.Resources>
<Style TargetType="ListBox" ... />
</DataTemplate.Resources>
...
<ContentPresenter Content="{Binding SpecialControl}" />
</DataTemplate>
Other possibilities are:
Have the view model actually derive from the Control class, then override OnApplyTemplate() and use GetTemplateChild to find a template item whose name starts with "PART_"
Implement an attached property that takes a property name, finds that property in the DataContext, and sets it to the DependencyObject to which the property is attached.
Implement your PropertySetter idea
My option #2 would look like this:
<DataTemplate TargetType="{x:Type MyViewModel}">
...
<TextBox local:PropertyHelper.SetViewModelToThis="SpecialControl" />
...
</DataTemplate>
The code in the SetViewModelToThis PropertyChangedCallback would get the view model from the DataContext, reflect on it to find the "SpecialControl" property, then set it to the TextBox. Note that the implementation of SetViewModelToThis must take into account the possiblity that DataContext is not set right away, and that it maybe changed requiring the old setting to be removed and a new one made.
First of all, the DataContext of the control should be the ViewModel object and not a property of it. Second, when you TwoWay bind a property of ViewModel to your control, changes in the control's value will update (in your case, 'set') the value of ViewModel's property.

MVVM Tabcontrol change tab

I am developing an mvvm app with wpf. A requirement just got added on to block the user from changing tabs if a textbox has text.
What is the best way to do this completely in the viewmodel? I don't know how to block a tabitem because there is no dependencyobject command in the tabcontrol to tie into, do i need to roll my own tabcontrol and build an ICommand around the SelectionChanged event?
Should I simply (eegad..don't say it) put code in the code behind of the view in the SelectionChanged event?
Do I have an alternative that I haven't thought of?
You might consider binding each of the TabItems' IsEnabled property to a property in your ViewModel (e.g. ViewModel.TabsEnabled) and set that property to False when the textbox has text. That way, you'll be able to enable/disable those tabs from your ViewModel without having to have a code behind file for that particular view.
Which means you'll have something like the following in your view (assuming your ViewModel is a static class named ViewModel):
<TabItem IsEnabled="{Binding Source={x:Static local:ViewModel.TabsEnabled}}"/>
Then you just have to set the TabsEnabled property on the ViewModel when one of the textboxes has content; there are a couple of ways to do this, but if they are bound to your ViewModel you should have plenty of opportunities to listen for changes and set TabsEnabled as appropriate.

Resources