Lazy loading System.Windows.Control.Image only when visible - c#

I need my application to render an image only when it becomes visible to the user. I tried attaching. I've tried the following (f#):
image.IsVisibleChanged.Add(fun e ->
if image.IsVisible & mtvCapture.Capture <> null then
mtvCapture.BeginCapture()
)
But this just loads, doesn't lazy load. How does IsVisible work, will this only be true when the users scrolls the image element into view?
Also tried modifying the binding source like so:
public ImageSource ImageElementSource
{
get
{
if (Capture == null)
{
BeginCapture();
return loadingImageSource;
}
CaptureToWpfImage();
return imageElement.Source;
}
}
How can I have BeginCapture() be called only when image is scrolled into view?

Sounds like you need something that supports Virtualization. This only creates the visible elements at load time. All other elements are created lazy when they get visible.
Example using VirtualizingStackPanel for a ListBox
<ListBox Name="c_imageListBox">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ImagePath}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Related

Get an Image that is the child of a Panel

I have a panel and I added it in a Window as ItemsPanel of an ItemsControl
<Grid x:Name="outerGrid" >
<ItemsControl ItemsSource="{Binding ImageSourcesCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<view:CustomPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
As you can see in this panel I show a series of images. Everything works all right, but now I want to access those images from inside the CustomPanel to change the Source to one of them.
If I access them as elements of base.Children I obtain a ContentPresenter, i.e.:
var element = base.Children[i]; //<- this is a ContentPresenter
So my question is: how can I get the Image?
One solution, since I know the position of the image, would be to get the element at that position. But I would prefer something else because it is not really clean and if I have other images moving around it can be a source of troubles.
Thanks!
I found out that if you just want to change the source of the Image (and this was my case), you can change the property Content of the ContentPresenter. Not sure how to access the Image though...

WPF Loading controls taking huge time

I have a string array like
string[] strngData = new string[] {"12","11","23","34"};
This string array length may be up to 4000. I need create a textbox for each string and loading all the text boxes inside a stackpanel within scrollviewer.
As the number of strings increases and number of textboxes increases resulting, the time to render the controls to UI is taking more time. for displaying 4000+ strings it is taking around 18+ secs.
Is there a way to improve the rendering time?
n place of stackpanel you may use ListView (which has inbuild deferred UI loading) with custom ItemTemplate and ItemsPanelTemplate:
<ListView Name="x" ItemsSource="..." HorizontalContentAlignment="Stretch" VirtualizingPanel.IsVirtualizing="True">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel HorizontalAlignment="Stretch" Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=/}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
There are some key moments: you have to use VirtualizingStackPanel in ItemsPanel template in place of usual StackPanel; TextBox.Text binding path have to be equal to /(working with current item); I also test the example for ObservaleCollection so using of List may disallow you to edit items position, count, etc.

Why when I switch my semantic zoom, it does not navigate to the section?

Something is strange with my semantic zoom. I have two sections in it:
And when I set the the ZoomOut, the grouping is okay, here is the image:
But for example if I choose the second option, semantic zoom does not navigate me to the item clicked.
Here are the most important parts of my program.
<!-- from resources -->
<CollectionViewSource
x:Name="groupedItemsViewSource"
Source="{Binding Groups}"
IsSourceGrouped="False">
...
<!-- Horizontal scrolling grid used in most view states -->
<SemanticZoom x:Name="semanticZoomControl" Grid.Row="1" >
<SemanticZoom.ZoomedInView>
<ListView x:Name="itemGridView" SelectionMode="None" IsItemClickEnabled="False"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollMode="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollMode="Auto"
Margin="0,-3,0,0"
Padding="116,0,40,46"
ItemTemplateSelector="{StaticResource StartItemSelector}"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemContainerStyle="{StaticResource ListViewItemStyleFlat}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</SemanticZoom.ZoomedInView>
<SemanticZoom.ZoomedOutView>
<ListView x:Name="groupGridView" CanDragItems="False"
CanReorderItems="False" SelectionMode="None" IsItemClickEnabled="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollMode="Auto"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollMode="Disabled"
ItemContainerStyle="{StaticResource ListViewItemStyleSimple}"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplateSelector="{StaticResource ZoomedOutSelector}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Height="330"
HorizontalAlignment="Left" VerticalAlignment="Top" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
What could be the reason which is happening this?
If you feel more confortable, you can download the project from SkyDrive: http://sdrv.ms/Ma0LmE
You need to set the ItemsSource of the Zoomed Out GridView in the codebehind like
groupGridView.ItemsSource = groupedItemsViewSource.View.CollectionGroups;
You'll most likely need to update your templates for that Grid, to append a "Group." before your bindings.
Your ItemTemplateSelector will also stop working at it will be passed a DependencyObject rather than your bound group. You can cast the object to ICollectionViewGroup which has a Group property that you can cast to your model object etc.
It's all a pain in the ass but I can't find a better way at the moment.
My case was somewhat different but I decided to share it here, hope someone will find it useful. In my app I had to have two different datasources for the zoomed in/out views of the semantic zoom. This, of course, broke the link between the two, so I couldn't do what #Nigel suggested above and what would work in the general case. Instead, I had to handle scrolling myself.
So, I added an event handler for the view change event:
<SemanticZoom ViewChangeStarted="OnSemanticZoomViewChangeStarted">
...
</SemanticZoom>
Then I defined it in the codebehind:
private void OnSemanticZoomViewChangeStarted(object sender, SemanticZoomViewChangedEventArgs e)
{
// only interested in zoomed out->zoomed in transitions
if (e.IsSourceZoomedInView)
{
return;
}
// get the selected group
MyItemGroup selectedGroup = e.SourceItem.Item as MyItemGroup;
// identify the selected group in the zoomed in data source (here I do it by its name, YMMV)
ObservableCollection<MyItemGroup> myItemGroups = this.DefaultViewModel["GroupedItems"] as ObservableCollection<MyItemGroup>;
MyItemGroup myGroup = myItemGroups.First<MyItemGroup>((g) => { return g.Name == selectedGroup.Name; });
// workaround: need to reset the scroll position first, otherwise ScrollIntoView won't work
SemanticZoomLocation zoomloc = new SemanticZoomLocation();
zoomloc.Bounds = new Windows.Foundation.Rect(0, 0, 1, 1);
zoomloc.Item = myItemGroups[0];
zoomedInGridView.MakeVisible(zoomloc);
// now we can scroll to the selected group in the zoomed in view
zoomedInGridView.ScrollIntoView(myGroup, ScrollIntoViewAlignment.Leading);
}
As you can see, the hack is that the gridview first needs to be rewound for the ScrollIntoView to work properly. I suppose this is just another of Microsoft's "by design" decisions... :P

Why is the ListView only showing 12 items?

I took the standard Item GridView template and modified it a bit to fit my needs. I actually have changed very little of the template code.
I have a single group, and I have a lot of items in it (92 items). The listview does render some of them, but it only renders 12 of them. Why is that? How can I override that and make it display all of the items?
Here's a screenshot of me broken into the debugger as I'm setting the DefaultViewModel:
I add items to my listview like so (as I parse XML from a service):
DataSource.AddItem(new DataItem(... title, name, etc, DataSource.getGroup("gallery")));
Then in my DataSource class (this is exactly the same one as the sample, I just renamed it), I added this method:
public static void AddItem(DataItem item)
{
item.Group.Items.Add(item);
}
Here's what the XAML that renders this looks like (it's the same as the GridView template:
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="1,0,0,6">
<Button
AutomationProperties.Name="Group Title"
Content="{Binding Title}"
Click="Header_Click"
Style="{StaticResource TextButtonStyle}"/>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,80,0"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>
</GridView>
I'd really appreciate any help.
Grid application template limits amount of items displayed in each group to 12 for reasons explained in the comment below:
public class SampleDataGroup : SampleDataCommon
{
...
public IEnumerable<SampleDataItem> TopItems
{
// Provides a subset of the full items collection to bind to from a GroupedItemsPage
// for two reasons: GridView will not virtualize large items collections, and it
// improves the user experience when browsing through groups with large numbers of
// items.
//
// A maximum of 12 items are displayed because it results in filled grid columns
// whether there are 1, 2, 3, 4, or 6 rows displayed
get { return this._items.Take(12); }
}
}

Dragging item from DataGridDragDropTarget to Label

I'm using the ListBoxDragDropTarget from the Silverlight Toolkit (April 2010) with SL 4.
I want to drag items from the list box onto a Label and handle the drop event there.
However it seems a bit complicated. The regular Drop event of the Label never gets fired. I suppose that is because the Silverlight Toolkit has its own way of handling Drag & Drop which is only partially compatible.
Looking arround I found the Microsoft.Windows.DragDrop.DropEvent and attached a handler to this event. And it worked!! I got the Drop event. However I'm not sure how to get to the real object that was dragged (a string).
I tried e.Data.GetData(typeof(string)) but I got nothing. Looking at the available formats there is a System.Windows.Controls.ItemDragEventArgs object. Inside this I found an array of System.Collections.ObjectModel.Selection which then has an Item property. I suppose in this Item property I find my object, but the whole method seems a bit fragile and I'm not convinced that is the official way to do this.
Is there any better way?
U can also use another ListBox
For ex: include namespace
xmlns:toolKit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
Let us add the “ListBoxDragDropTarget inside the Grid. Set the attribute “AllowDrop” to True. Once it is set to true, it will be able to catch the drop event inside the control.
Now we will add a ListBox inside the ListBoxDragDropTarget and set properties whichever u like. suppose
<toolKit:ListBoxDragDropTarget AllowDrop="True">
<ListBox x:Name="customerListBoxMain" Height="200" Width="200"
DisplayMemberPath="Name">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</toolKit:ListBoxDragDropTarget>
And add another ListBox
<toolKit:ListBoxDragDropTarget AllowDrop="True">
<ListBox Height="200" Width="200" DisplayMemberPath="Name">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</toolKit:ListBoxDragDropTarget>
Now to fetch some data and set it to the Source of the first ListBox from code behind. Here is the sample code:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
customerListBoxMain.ItemsSource = PersonDataProvider.GetData();
}
}
It's Done..

Categories

Resources