-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor ItemContainerGenerator API #9497
Comments
Should Items vs ItemsSource be considered again as well: #7553 |
As an experiment, I ported of the WPF ICG a while back (which is the same in UWP, with a couple internal changes) so I've learned quite a bit about how they run the cycle of container generation (the port isn't public). After doing that, and seeing how they wire everything up, I think I actually prefer the WPF method more - although its certainly not perfect. The way I look at in WPF/WinUI, the ItemContainerGenerator is only responsible for maintaining the collection of generated items and the generation logic cycle, while the ItemsControl deals with what kind of containers to generate and how to initialize them for the UI. In Avalonia, the ICG handles almost everything, the ItemsControl just hosts the ICG. To me, the WPF way makes more sense and is less overhead in work since you never have to subclass the ICG. (BTW, I'm not trying to imply we port their ICG. Just laying out the differences for comparison since they split the workload up differently - as indicated above that's too much of a breaking change) The main differences:
Also, with newer UWP controls like the ListView/GridView, they rely less on the ItemContainerGenerator and have a slighly different way of running everything to support better virtualization. This newer logic is what the ItemsRepeater was derived from, so its probably somewhat similar to that, though this is all still closed source. Although, I agree, the need for the constant eventargs creation in the ItemsRepeater isn't the best. A couple other things:
I'd certainly be in favor of a hybrid API though for this. I think the main thing is to not need to subclass the ICG for a new container type. If you don't want to go the WPF route with the override methods in ItemsControl, then I think remove the generic version of the ICG and make public class ItemContainerGenerator
{
// Make this a settable property
Type ContainerType { get; set; }
// Equivalent to WPF IGeneratorHost.CreateContainerForItem, also replaces the existing CreateContainer method
Func<Control> CreateContainerFunc { get; set; }
public void Materialize(int index, object item)
{
if (typeof(item) == ContainerType) // Equivalent to WPF IGeneratorHost.IsItemItsOwnContainer
{
}
var container = CreateContainerFunc();
//... materialize
}
}
public class MyCustomItemsControl : ItemsControl
{
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator(...)
{
ContainerType = typeof(MyCustomContainer),
CreateContainerFunc = () => return new MyCustomContainer();
};
}
} On a slightly different but related note/an expansion of this topic though, I'd also like to see generation logic moved to panels, as is in WinUI/WPF. Currently, there is no way to provide custom virtualization logic since everything is handled through ItemsPresenter and that is locked to either No or simple virtualization. Also doing this, if desired, one could completely skip the ItemsPresenter and just let the panel do the work (adding the |
Thanks for the detailed reply @amwx, looks like you know more about the WPF/UWP/WinUI container generation than me! Without going into the details of the implementation, I think if I were to completely rewrite the Avalonia API from scratch ignoring compatibility with Avalonia itself and WPF/UWP/WinUI I'd not even have the
I think if we're going to break the Avalonia API to that extent then I'd be in favor of essentially deprecating the
This is a good point, and something that we should try to support; if not initially then we should make sure we bear it in mind during API design. I guess my question is: Would people mind us breaking the API in this area for 11.0? How common is it to define a new If the feedback is that we'd be OK with breaking the API, I'll have a think about what I think my ideal API would look like and post it here for discussion. |
Yep, this will be addressed before 11.0! |
For most people just consuming Avalonia, probably pretty rare. Its more useful if building a control library (which is where my POV comes from).
This is an interesting thought. Actually if you look at the docs for UWP, they say not to use those |
@amwx investigating an API proposal now, but I have a question: any idea why item containers in WPF/UWP are |
@amwx another question regarding this:
Do you have an example of a WPF/UWP control which does this (with source if possible)? |
No clue. Just had a quick browse over
I thought I saw once that UWP will sometimes skip the ItemsPresenter and just inject the panel straight into its VisualChildren collection, but I can't seem to replicate that now, and I think WPF always uses one. But in both WPF and UWP/WinUI its completely valid to do this and the panel will still populate: <Setter Property="Template">
<ControlTemplate TargetType="ItemsControl">
<StackPanel IsItemsHost="True" />
</ControlTemplate>
</Setter> (In UWP, IsItemsHost isn't settable and is automatically calculated) If you look at the Panel source, it shows how it hooks up to the parent ItemsControl/Generator and generates the items itself. Trying to get rid of ItemsPresenter isn't all that important, its just one thing WPF/UWP support that isn't currently possible (or at least easy). What's more important is to shift the generation logic into the panels. There's at least 3 reasons I can think of: 1- Logical scrolling. In WPF, you can write a custom panel that implements |
Wow, I didn't even know that was possible! I'm not a big fan of putting the code for generating items in simple (non-virtualizing) panels like Having said that, in the case of actual virtualizing panels, the reasons for moving generation logic into the panel are good ones. My initial thoughts on a potential design:
Perhaps in the last case, we can say that |
That's a fair point and in-line with how Avalonia generally separates things now, so let me revise my statement: I think its more important to open up the API to external/alternate options, where the generation actually happens an how we get from ItemsControl to panel is mostly irrelevant.
I was thinking something similar. I thought about this some even before this discussion and my thought to avoid breaking existing code was to have an interface (or base panel class if you want to avoid interfaces, |
Good afternoon. CommandItemContainerGeneratorBase.cs public abstract class CommandItemContainerGeneratorBase: ItemContainerGenerator<Control>
{
public CommandItemContainerGeneratorBase( IControl owner, AvaloniaProperty contentProperty, AvaloniaProperty contentTemplateProperty )
: base( owner, contentProperty, contentTemplateProperty )
{
}
protected sealed override IControl CreateContainer( object item )
{
if( item is ICommandItem cmdItem )
{
return GetItemContainer( cmdItem );
}
return null;
}
protected abstract Control GetItemContainer( ICommandItem cmdItem );
} MenuItemContainerGenerator.cs public class MenuItemContainerGenerator: CommandItemContainerGeneratorBase
{
public MenuItemContainerGenerator( IControl owner, AvaloniaProperty contentProperty, AvaloniaProperty contentTemplateProperty )
: base( owner, contentProperty, contentTemplateProperty )
{
}
protected override Control GetItemContainer( ICommandItem commandItem )
{
switch( commandItem.Type )
{
case CommandItemType.MenuItem:
return new MenuItem();
case CommandItemType.Separator:
return new MenuItemSeparator();
case CommandItemType.Custom:
return commandItem.GetItemContainer();
default:
throw new ArgumentException();
}
}
} Now we want to upgrade to preview-6 version and keep exactly the same logic. However, we can't do this because the
In WPF version of our Application we override MenuEx.cs public class MenuEx: Menu
{
private ToolStripItemViewModel m_currentItem;
protected override bool IsItemItsOwnContainerOverride( object item )
{
var currentItem = item as ToolStripItemViewModel;
if( currentItem != null )
{
m_currentItem = currentItem;
return false;
}
return base.IsItemItsOwnContainerOverride( item );
}
protected override DependencyObject GetContainerForItemOverride()
{
if( m_currentItem != null )
{
var item = m_currentItem;
m_currentItem = null;
switch( item.Type )
{
case WpfToolStripItemType.MenuItem:
return new TopLevelMenuItem();
case WpfToolStripItemType.Separator:
return new MenuItemSeparator();
default:
throw new ArgumentException();
}
}
return base.GetContainerForItemOverride();
}
} Can you please help how we can keep the same logic of preview-4 and WPF Applications in new preview-6 API? |
That's a good question @Tulesha and one that's not addressed with the current design (or in WPF, I don't think). It is however a requirement we've discussed with customers.
The problem with this is that it won't work with recycling because consider the following:
I think to handle this, we need to add an extra step to get a recycle key for an item. So the flow would go something like:
Thoughts? |
@grokys, that sounds good. I would like this thing to work for |
The
ItemContainerGenerator
API needs a rework; in particular its materialized events are rather confused and don't really fire when they should.We need to decide a few things:
ItemContainerGenerator
? Personally I've always found this API confusing as it splits container generation between theItemsControl
and the generator, with the generator just being used to signal that it's creating "batches" and the actual containers being created by theItemsControl
itself with a bunch of overridable methods thereElementFactory
API is much nicer though it still has things I don't like much - mainly the fact that one needs to create a newElementFactoryGetArgs
object for each container generated. In particular though theItemsRepeater
events for prepared/cleared/index changed are much nicer than having to override methods in WPF/UWPWe probably want a hybrid of these APIs, which leave the main part of the API unchanged from 0.10.x for the simple use-case of defining a new container type.
The text was updated successfully, but these errors were encountered: