I'm using several lists across my project instead of trees - for proper virtualization (a lot of items in tree structure).
Those lists are pretty much the same. The only difference is in DataTemplates. Those lists have a few events bound, which I have to copy & update in several places. Current events are used to:
prevent horizontal auto-scrolling
support for arrow keys to navigate through tree structure
I found no way to bind events in a single style in resource dictionary, as events must belong to specific class. So I have to copy exactly same events between classes and bind them to specific lists. That is quite a lot of text, both in XAML and code.
What I wanted to do is to define a new user control, deriving EVERYTHING from standart ListBox, but overriding a few minor methods (instead of events). And reuse this control everywhere where I need such a list without having to copy all the events.
Problem is - it requires me to define custom <UserControl ... />. Is there a way to just use ListBox template/style there? I need no GUI modifications from standart ListBox.
I could be missing some simple way to perform what I want. I'd appreciate any way to do this.
Not sure about your setup but you will probably have to override the ListBox and ListBoxItem. Then override some methods :
public partial class MyListBox: ListBox
{
protected override System.Windows.DependencyObject GetContainerForItemOverride()
{
return new MyListBoxItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is MyListBoxItem;
}
}
public class MyListBoxItem : ListBoxItem
{
}
This will force your containers to be ListBoxItem overrides.
Now you just have to implement yous specific code tof keys in ListBoxItem overrides. If you don't need any style changes the default ListBox style will be applied.
Now you can use it in your XAML:
<local:MyListBox ItemsSource="{Binding Items}">
<local:MyListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding id}"/>
</DataTemplate>
</local:MyListBox.ItemTemplate>
</local:MyListBox>
Related
I need to display ListView with different items (in total 10-15 item types). For this I use DataTemplateSelector. But this causes ListView odd behavior during scrolling: at some point it jumps to the top of list view. I've found this article for UWP: https://msdn.microsoft.com/en-us/windows/uwp/debug-test-perf/optimize-gridview-and-listview. It says that ItemTemplateSelector supports only 5 DataTemplates
Additionally, an item template selector only considers five possible candidates when evaluating whether a particular container can be reused for the current data item.
I think this is the reason. I've tried to reduce the number of DataTemplates returned by my DataTemplateSelector and it solved the issue: scrolling works as expected. But how can I solve this issue without reducing number of DataTemplates? I know that I can disable virtualization, but I would like to keep it enabled if possible.
For UWP there is an option to use ChoosingItemContainer event, but it isn't available for WinRT.
Is it possible to solve this issue without disabling UI virtualization in WinRT?
This is what I ended up doing in my project (I have a listview with infinite scrolling). Basically, I did a part of virtualization myself.
I removed DataTemplateSelector completely. Instead, I use one template for all the items:
<ListView
...
>
<ListView.ItemTemplate>
<DataTemplate>
<messages:MyCustomContainer />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Where MyCustomContainer is a simple UserControl:
<UserControl
x:Class="MyCustomContainer"
...
DataContextChanged="OnDataContextChanged"
>
<Grid x:Name="Container"/>
</UserControl>
I instantiate and select appropriate nested template in code behind of MyCustomContainer:
void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
var context = DataContext as MyModelThatHelpsDecideOnAppropriateVisualTemplate;
if (context == null) {
// this means, item has been removed from the list and cached (we call this 'virtualization')
Container.Children.Remove(CurrentTemplate);
ReleaseTemplate(CurrentTemplate); // clear and cache our concrete template
CurrentTemplate = null;
} else {
// this means, we need to get a concrete template
// ... some logic to decide on the proper visual template type
Type templateType = GetTemplateTypeForData(context);
// ... some logic to get visual template from cache
CurrentTemplate = GetTemplateFromCache(templateType);
Container.Children.Add(CurrentTemplate);
}
}
On the bright side, this works fine (does for me, and I have around a dozen item templates).
On the other side, this way the UI framework only virtualizes MyCustomContainer list items, and you have to cache concrete visuals yourself. In my example, you have to store instances of your 10-15 templates in some cache, and implement GetTemplateTypeForData(), GetTemplateFromCache() and ReleaseTemplate()... But that should be really straightforward, took around 100 lines of code for me.
Basically, I'm not sure how to use MVVM, and/or use commands correctly in my current situation. So, I have a View, containing a list box, and a panel of animation objects, that I created. These animation objects can be animated through a simple public method, Animate(). The goal here, is to associate this Animate() method with buttons inside the list box, like so:
As we can see in the diagram below, both the ListBox items and the visual elements inside of the animation area are associated with the same collection of models from the ViewModel, with the items in each being templated. For example, the ListBox items are simply defined to have some text related to a data item, and the AnimationObjects take on an appearance according to the data. These models, I feel, should not understand that an animation is occurring - they're simple data, and the animation does not change them.
Finally, I show in the below diagram, that I have created two FrameworkElement child types, one for holding animation objects, and another that defines these animation objects.
How can I connect this animation action to the buttons within the list box? It doesn't make sense to me that the models/viewmodels know about the animation, because it doesn't change the state of anything in my application - it's just for visual purposes. I've thought about using a RoutedCommand defined in AnimationObject, and having the buttons bind their command property accordingly, but I worry that will simply make every element animate at the same time.
It is also important for my sake, that I conform to MVVM, as these data will be used in many other situations, perhaps even a different version of this view.
Any advice would be appreciated.
What you can do is call a command in your ViewModel , i.e. the DataContext of your ListBox.
CS :
public class ViewModel
{
public ICommand AnimateObjectCommand { get; }
}
XAML :
<DataTemplate x:Key="AnimationObjectItemTemplate">
<Button Command="{Binding Path=DataContext.AnimateObjectCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}" />
</DataTemplate>
<ListBox ItemsSource="{Binding AnimationObjects}" ItemTemplate="{StaticResource AnimationObjectItemTemplate}"/>
your Command implementation should be one that accepts an argument which would be passed by the CommandParameter .
private ICommand _animateObjectCommand;
public ICommand AnimateObjectCommand
{
get
{
if (_animateObjectCommand == null)
{
_animateObjectCommand = new RelayCommand<AnimationObject>( ao => { ao.Animate(); });
}
return _animateObjectCommand;
}
}
The CommandParameter = {Binding} meaning this.DataContext where this is an Item in your ListBox and it's DataContext is an AnimationObject.
seems like a trivial task: i am building a wpf application, using MVVM pattern. what i want is dynamically change part of a view, using different UserControls, dependent on user input.
let's say, i have got 2 UserControls, one with a button, and another with a label.
in main view i have a container for that. following XAML "works":
<GroupBox Header="container" >
<local:UserControlButton />
</GroupBox>
and a UserControl element with buttons pops up. if i change it to another one, it works too.
question is how to feed that groupbox dynamically. if i put something like that in my model view:
private UserControl _myControl;
public UserControl MyControl
{
get
{
return _myControl;
}
set
{
_myControl= value;
InvokePropertyChanged("MyControl");
}
}
and change my view XAML to something like:
<GroupBox Header="container" >
<ItemsControl ItemsSource="{Binding MyControl}" />
</GroupBox>
and feed it from command with usercontrol for button or for label: nothing happens, although "MyControl" variable is set and is "invoke property changed"..
Obviously there are many ways to skin this particular cat - but to answer the question of why it doesn't work you need to look into the ItemsSource property of ItemsControl on MSDN.
The items control is designed to show multiple items, provided through an IEnumerable passed to the ItemsSource property. You are passing a UserControl, so the binding will fail.
For your example, I would change the ItemsControl to a ContentControl and bind the content to your MyControl property. This should then work.
<GroupBox Header="container" >
<ContentControl Content="{Binding MyControl}" />
</GroupBox>
However, I would strongly recommend looking into other ways of doing this - having a control in your VM breaks MVVM to my mind. Depending on what you are doing look at data templates - #Sheridan's link in the comments provides an great description of a way to do it.
Couldn't post this as a comment so adding as answer..
Have a look at this:
Implementing an own "Factory" for reusing Views in WPF
It uses DataTemplates but doesn't require the DataTemplate section for each view. If you potentially have a lot of user controls/views you wish to display or you are reusing through multiple views or you are intending to actually dynamically generate a view (versus just loading an existing user control) then this might suite your needs.
I am using WPF(4.5) and Caliburn.Micro. I am trying to understand how to have an "event" in my View manipulate other controls in my View.
For instance:
My View has an Expander Control, a Button, and a GridView. The GridView is inside the Expander. When the user clicks the button it calls a method in the VM that populates the gridview with a BindableCollection<>. What I want to have happen is when that collection has more then 1 item I want to Expand the Expander Control automatically.
Ideas?
You can bind to the number of items in a collection:
<Expander IsExpanded="{Binding Path=YourCollection.Length, Converter={StaticResource ResourceName=MyConverter}" />
and then in the window or usercontrol:
<UserControl... xmlns:converters="clr-namespace:My.Namespace.With.Converters">
<UserControl.Resources>
<converters:ItemCountToBooleanConverter x:Key="MyConverter" />
</UserControl.Resources>
</UserControl>
and the converter:
namespace My.Namespace.With.Converters {
public class ItemCountToBooleanConverter : IValueConverter
{
// implementation of IValueConverter here
...
}
}
I wrote this out of my head, so apologies if it contains errors ;)
Also: Make sure your viewModel implements the INotifyPropertyChanged interface, but I assume you already know that.
#cguedel method is completely valid but if you don't want to use Converters (why one more class) then in your view model have another property of type bool maybe called ShouldExpand, well why talk so much, let me show you:
class YourViewModel {
public bool ShouldExpand {
get {
return _theCollectionYouPopulatedTheGridWith.Length() != 0;
// or maybe use a flag, you get the idea !
}
}
public void ButtonPressed() {
// populate the grid with collection
// NOW RAISE PROPERTY CHANGED EVENT FOR THE ShouldExpand property
}
}
Now in your View use this binding instead:
<Expander IsExpanded="{Binding Path=ShouldExpand}" />
As i said before the other solution is well but i like to reduce the number of classes in my solutions. This is just another way of doing it.
I've currently got XAML code like this:
<ListView Name="fileLV" SelectionMode="Extended" ItemsSource="{Binding path=DataContext.SelectedAsset.Files,ElementName=selectionView,IsAsync=True}"/>
That "Files" property takes fifteen seconds to return. The whole time the user is wondering what's going on. I've seen some other code to show the fallback value or use multiple bindings, but those don't rely imply "leave this control alone" like an hourglass over that control would imply.
What I want is to be able name a binding and then bind some other properties to that binding's IsBusy property. I want a trigger to change the cursor on that listview while his binding is busy. Is there any existing WPF framework help for this?
I don't know of any built-in, out-of-the-box solution but there sure are ways to make a nice experience out of it.
I will give you the quick idea of how I would build this and if you need I can come up with the code as well:
Create a "LoadingItem" DataTemplate that would show an progress bar of some kind as an item of your list
Create a "DataTemplateSelector" to switch between the LoadingItem
and the RegularItem of your list.
In your Files property, clear the collection and add an item that
will be shown as LoadingItem (depends on how you built your
DataTemplateSelector logic. Start another thread to scan for files
and fill a return the results in a temporary collection
(BackgroundWorker). When the method returns, you are on the UI
thread again, clear your ItemsSource collection again and fill it
with the results.
For this do not use IsAsync. On the Property use a BackGroundWorker. First return a source with a "working message", start BackGroundWorker, then on the callback supply the real source and call NotifyPropertyChanged. You can even have a progess bar.
I was able to make the DataTemplateSelector work. One caveat was that all the bindings for the ListView need to be enumerable. In my control I added a resource like this:
<UserControl.Resources>
<x:Array x:Key="LoadingTemplate" Type="DataTemplate">
<DataTemplate>...my daisy code...</DataTemplate></x:Array>...
Then I changed my binding to look like this:
<ListView.ItemsSource>
<PriorityBinding>
<Binding Path="DataContext.SelectedAsset.Files" ElementName="selectionView" IsAsync="True"/>
<Binding Source="{StaticResource LoadingTemplate}" />
</PriorityBinding>
</ListView.ItemsSource>
Then I installed this template selector:
public class OverridableDataTemplateSelector: DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return item as DataTemplate ?? base.SelectTemplate(item, container);
}
}