Is there a way, for the FlipView control, to ONLY have it load the selected item??
The default style of a FlipView, from Microsoft's styles, uses a VirtualizingStackPanel:
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel AreScrollSnapPointsRegular="True" Orientation="Horizontal" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
What occurs is that the current element and adjacent elements will begin to load. What I would like for the FlipView to do is only load the item when it's shown (in other words, when it becomes the selected item).
Is that possible?
You could make your custom classes:
public class ImageGallery : FlipView
{
public ImageGallery()
{
SelectionChanged += (s, a) =>
{
((ImageItem)Items[SelectedIndex]).Load()
}
}
}
public class ImageItem : FlipViewItem
{
public ImageItem(SomeType yourImageInfo)
{
Content = new YourControl(yourImageInfo);
}
public void Load()
{
//load your image
}
}
Here's my problem:
I'm using a flipview which contains items that consist of a custom control that load an image asynchronously. I only want to load the image of the selected index. So when the flipview loads, the first item loads. If the user swipes left, now the second image loads, and so on.
If you have a data-bound ImageSource, you can do it in such a way, that you manually force the load of the current item when the selection of the FlipView changes.
You can create a custom item class. Note that it implements the INotifyPropertyChanged interface so that it notifies the control when the image is loaded:
public class FlipViewItemViewModel : INotifyPropertyChanged
{
private bool _isLoaded = false;
private ImageSource _imageSource = null;
public ImageSource ImageSource
{
get
{
return _imageSource;
}
set
{
_imageSource = value;
OnPropertyChanged();
}
}
/// <summary>
/// Forces the loading of the item
/// </summary>
public void ForceLoad()
{
//prevent loading twice
if ( !_isLoaded )
{
_isLoaded = true;
//load the image (probably from network?)
ImageSource = new BitmapImage(
new Uri( "ms-appx:///Assets/StoreLogo.png" ) );
}
}
/// <summary>
/// INotifyPropertyChanged implementation
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(
[CallerMemberName] string propertyName = null )
{
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
}
In XAML, you have to bind the data source to your FlipView using ItemsSource and handle the SelectionChanged event:
<FlipView x:Name="FlipControl"
ItemsSource="{x:Bind Items}"
SelectionChanged="Selector_OnSelectionChanged">
Inside the SelectionChanged handler you then manually call the ForceLoad method for the current item.
private void Selector_OnSelectionChanged( object sender, SelectionChangedEventArgs e )
{
//get the currently selected item
var currentItem = FlipControl.SelectedItem as FlipViewItemViewModel;
//force-load it
currentItem?.ForceLoad();
}
I have made a GitHub sample with this solution and example.
Related
I'm trying to create a custom control that has a header and a footer and body. The idea is that the body of the report is a custom stack panel control that will allow the user to indicate page orientation and grouping. I created a dependency property on the custom UC to accept an IList of the custom stack panel. What I am trying to do is bind to one of the stack panels in the list. But for some reason the binding is not working.
The ReportPage:
public class ReportPage : StackPanel
{
//Nothing right now but will eventually include controls for page orientation and size (8.5x11, 11x17, etc.)
}
The UserControl code behind:
public partial class Report : UserControl, INotifyPropertyChanged
{
public Report()
{
ReportPages = new List<ReportPage>();
}
public static readonly DependencyProperty =
DependencyProperty.Register("ReportPages", typeof(IList), typeof(Report));
public IList ReportPages
{
get => (IList)GetValue(ReportPagesProperty);
set
{
SetValue(ReportPagesProperty, value);
ActivePage = value[0];
OnPropertyChanged(nameof(ActivePage));
}
}
private ReportPage _activePage;
public ReportPage ActivePage
{
get => _activePage;
set
{
_activePage = value;
OnPropertyChanged(nameof(ActivePage));
}
{
}
The UserControl xaml:
<Grid>
<!--Some xaml for the header and footer.-->
<ContentControl Content="{Binding ActivePage, RelativeSource={RelativeSource, FindAncestor, AncestorType=local:Report}}"/>
</Grid>
Here is how I am consuming the custom control. This should, in my mind at least, make three "pages" which I can toggle between using a button control that I didn't share.
<reportEngine:Report>
<reportEngine:Report.ReportPages>
<reportEngine:ReportPage>
<TextBlock>This is Page 1</TextBlock>
</reportEngine:ReportPage>
<reportEngine:ReportPage>
<TextBlock>This is Page 2</TextBlock>
</reportEngine:ReportPage>
<reportEngine:ReportPage>
<TextBlock>This is Page 3</TextBlock>
</reportEngine:ReportPage>
</reportEngine:Report.ReportPages>
</reportEngine:Report>
Any Ideas why the binding isn't working?
So I at least found a quick work around. I utilized the Collection Changed Event handler pattern from this answer and modified it for static dependency properties. Then, to get the values from the collection bound to the dependency property I create a static instance of the Report object in the constructor and use that to pass various values back to the object from the collection. Something like this:
public partial class Report : UserControl, INotifyPropertyChanged
{
private static Report _thisReport;
public Report()
{
InitializeComponent();
ReportPages = new ObservableCollection<ReportPage>();
_thisReport = this;
}
public static readonly DependencyProperty ReportPagesProperty =
DependencyProperty.Register("ReportPages", typeof(IList), typeof(Report), new FrameworkPropertyMetadata(ReportPagesChanged));
public IList ReportPages
{
get => (IList)GetValue(ReportPagesProperty);
set
{
SetValue(ReportPagesProperty, value);
//Update some other properties associated with the control (Total Page Numbers, etc.)
}
}
private static void ReportPagesChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
var newColl = (INotifyCollectionChanged)eventArgs.NewValue;
if (newColl != null)
newColl.CollectionChanged += ReportPages_CollectionChanged;
var oldColl = (INotifyCollectionChanged)eventArgs.OldValue;
if (oldColl != null)
oldColl.CollectionChanged -= ReportPages_CollectionChanged;
}
private static void ReportPages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs eventArgs)
{
var newPages = (IList<ReportPage>) sender;
//Updates properties of the Report control.
_thisReport.ActivePage = newPages[0];
_thisReport.TotalPageNumber = newPages.Count;
}
}
Whether this is "correct" or not I couldn't say, but it works. If someone has a better answer I will change the answer.
Say we have a grid view which is binding with the data source MyInformation. One of column is a check box. I want to bind something with it.
ItemsSource="{Binding MyInformation}"
In the ViewModel.
public ObservableCollection<Container> MyInformation
{
get
{
if (this.myInformation == null)
{
this.myInformation = new ObservableCollection<Container>();
}
return this.myInformation;
}
set
{
if (this.myInformation != value)
{
this.myInformation = value;
this.OnPropertyChanged("MyInformation");
}
}
}
The class Container has a member "GoodValue".
public class Container
{
public bool GoodValue {get;set;}
//
}
I have the checkbox bind with the member.
<DataTemplate>
<CheckBox HorizontalAlignment="Center" IsChecked="{Binding GoodValue, Converter={StaticResource ShortToBooleanConverter}}" Click="CheckBox_Checked"></CheckBox>
</DataTemplate>
I don't have the property GoodValue created in ViewModel as I think GoodValue is a member of Container. The ObservableCollection includes it automatically.
The problem is each time I read the data from the database. The checkbox is unchecked. So I doubt my code. Thanks for hint.
You can do two things:
Check if there are some binding errors
Implement INotifyPropertyChanged interface into your class Container.
public class Container:INotifyPropertyChanged
{
private bool _goodValue;
public string GoodValue
{
get
{
return _goodValue;
}
set
{
_goodValue = value;
OnPropertyChanged("GoodValue");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public event PropertyChangedEventHandler PropertyChanged;
}
The ObservableCollection is usefull if you want to notify to your view when a new item is inserted or deleted from the collection, but if the object contained inside it doesn't implement InotifyPropertyChanged, the changes to properties of that object won't affect any change to your view.
I have an image withn name fb.png and it's in root project(prtable) and I add this image to Resource>drawble in Droid project.
Thees is my MainPage.xaml code:
<Image x:Name="img1"></Image>
And Thees is my MainPage.xaml.cs code:
public MainPage()
{
InitializeComponent();
ImageSource img = ImageSource.FromResource("App2.fb.png");
img1.Source = img;
img1.Aspect = Aspect.AspectFit;
img1.BackgroundColor = Color.Navy;
}
What changed is need that image will be appeared?
If the file is saved in the Resources/Drawable directory, then you use FromFile, not FromResource. FromResource is used for images packaged as embedded resources in your built library.
You also need to specify the exact name of the file as it appears in Resources/Drawable, so this should do it:
public MainPage()
{
InitializeComponent();
ImageSource img = ImageSource.FromFile("fb.png");
img1.Source = img;
img1.Aspect = Aspect.AspectFit;
img1.BackgroundColor = Color.Navy;
}
Here is a full implementation of MVVM bound image resource to Image control. You need to set your viewmodel as the context of your page where the XAML is. Also accessing as "App2.fb.png" seems odd, it should just be fb.png. That might be a simpler fix.. just rename the image source to the exact name of the image as listed in Droid > resources
XAML
<Image
Aspect="AspectFit"
Source="{Binding PropertyImageStatusSource}">
Base ViewModel
Have your viewmodels inherit from a viewmodel base class so INotifyPropertyChanged is implemented on your accessors universally.
public class _ViewModel_Base : INotifyPropertyChanged
{
//figure out what is getting set
public virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
//attach handler with property name args to changing property, overridable in inheriting classes if something else needs to happen on property changed
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
ViewModel
Public MyViewModel : _ViewModel_Base
{
private string ImageStatusSource = "fb.png";
public string PropertyImageStatusSource
{
set { SetProperty(ref ImageStatusSource, value); }
get { return ImageStatusSource; }
}
}
Below is the xaml and c# code for handling selected items in my gridview.
I am also using MVVM Light and everything is working, including me being able to see what's inside SelectedItems.
However I when I attempt to clear the SelectedItems, my UI doesn't seem to update/reflect the changes made to SelectedItems.
I am using WinRT XAML Toolkit (http://winrtxamltoolkit.codeplex.com/) which has the BindableSelection extension on a GridView
XAML
<controls:CustomGridView
x:Name="VideoItemGridView"
Grid.Row="2"
Margin="0,-3,0,0"
Padding="116,0,40,46"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
IsItemClickEnabled="True"
SelectionMode="Extended"
Extensions:GridViewExtensions.BindableSelection="{Binding SelectedVideoItems, Mode=TwoWay}"
ItemsSource="{Binding Source={StaticResource ViewSource}}"
ItemTemplate="{StaticResource VideoItemTemplate}">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid ItemWidth="250" ItemHeight="160" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</controls:CustomGridView>
MyViewViewModel.cs
#region Selected Items
/// <summary>
/// Gets or sets the selected video items.
/// </summary>
public ObservableCollection<object> SelectedVideoItems
{
get { return this._selectedVideoItems; }
set
{
this._selectedVideoItems = value;
this.Set("SelectedVideoItems", ref this._selectedVideoItems, value);
}
}
private ObservableCollection<object> _selectedVideoItems = new ObservableCollection<object>();
#endregion
#region App Bar Click Commands
/// <summary>
/// Gets the ClearSelection click command.
/// </summary>
public ICommand ClearSelectionClickCommand
{
get
{
return new RelayCommand(() => this.ClearSelectionOperation());
}
}
/// <summary>
/// Selects all command operation.
/// </summary>
private void ClearSelectionOperation()
{
this.SelectedVideoItems = new ObservableCollection<object>();
}
#endregion
Try clearing your selected items in ClearSelectionOperation by calling
this.SelectedVideoItems.Clear();
instead of
this.SelectedVideoItems = new ObservableCollection<object>();
If that doesn't help check if the current version of the extension from March 7 fixes the problem.
It turns out that since I am using a data template, it is actually my data model that needed to set a flag to indicate it is selected
Here's the missing piece of the puzzle. Once I update the data model bound to the grid view item (which also includes support for row/col spanning), the UI updated as expected.
Hope this helps others.
public class CustomGridView : GridView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
try
{
base.PrepareContainerForItemOverride(element, item);
dynamic _Item = item;
element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, _Item.ColumnSpan);
element.SetValue(VariableSizedWrapGrid.RowSpanProperty, _Item.RowSpan);
element.SetValue(GridViewItem.IsSelectedProperty, _Item.IsSelected);
}
catch
{
element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, 1);
element.SetValue(VariableSizedWrapGrid.RowSpanProperty, 1);
element.SetValue(GridViewItem.IsSelectedProperty, false);
}
finally
{
base.PrepareContainerForItemOverride(element, item);
}
}
I'd like to use a FlipView to display some items and start showing a specific item.
For this, I have defined a view model class:
class MyDataContext
{
public MyDataContext()
{
Items = new List<MyClass>();
Items.Add(new MyClass("1"));
Items.Add(new MyClass("2"));
Items.Add(new MyClass("3"));
SelectedItem = Items[1];
}
public List<MyClass> Items { get; set; }
public MyClass SelectedItem { get; set; }
}
As you can see, the selected item is not the first item.
Now for the XAML:
<FlipView ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}"></FlipView>
However, when I run the app, the flip view shows the first item, not the second item.
Is this intentional?, or is it a bug?
Try this
<FlipView
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
your SelectedItem needs to be a TwoWay binding for it to work, since the value is set by both the control and the view model.
Was having the same issue with the FlipView and unable to get the BindableBase or the TwoWay option to work. Because the order of the list was not really a topic for me, I've created a method to reorder the ItemsSource, to start with the SelectedItem as being the first item in the Collection.
In the underlying code, the result is the new ItemsSource for the FlipView, instead of the previous List elements.
public static List<T> ReorderList(List<T> elements, T selectedElement)
{
var elementIndex = elements.FindIndex(x => x.Id == selectedElement.Id);
var result = new List<T>();
foreach (var item in elements)
{
if (elementIndex .Equals(elements.Count))
{
elementIndex = 0;
}
result.Add(elements[elementIndex]);
elementIndex++;
}
return result;
}
On top of what Filip stated, you're class (MyDataContext) needs to notify the UI that the property has changed. Your ViewModel must implement INotifyPropertyChanged and the property needs to fire the PropertyChanged event
public class ViewModel : INotifyPropertyChanged
{
private object _selectedItem;
public object SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
}
You can also use the BindableBase class that comes with the sample apps
public class ViewModel : BindableBase
{
private object _selectedItem;
public object SelectedItem
{
get { return this._selectedItem; }
set { this.SetProperty(ref this._selectedItem, value); }
}
}
It's look like a bug.
If you debug your code you will notice that at first your SelectedItem in VM set to the right element, then it sets to null and after that it sets to the first element of FlipView's ItemsSource collection.
As a workaround I see setting SelectedItem of VM after Loaded event of FlipView is raised.