Hi I am trying to upgrade our ios app from mvvmcross v1 to v3. I can't figure out how to make my custom buttonrow work.
My view ViewDidLoad it is the button items that binds to the button row
public override void ViewDidLoad()
{
base.ViewDidLoad();
SortingView.ViewModel = ViewModel;
_shown = false;
// Setup View Animatons
Buttons.OnClick = () => { AnimationTransition = ViewTransitionAnimation.TransitionFade; };
TopRightButton.TouchDown +=
(sender, args) => {
AnimationTransition = ViewTransitionAnimation.TransitionCrossDissolve;
};
// Setup Bindings
this.AddBindings(
new Dictionary<object, string>
{
{this.BackgroundImage, "{'ImageData':{'Path':'BackgroundImage','Converter':'ImageItem'}}"},
{this.TopbarBackground, "{'ImageData':{'Path':'TopBarImage','Converter':'ImageItem'}}"},
{this.TopLogo, "{'ImageData':{'Path':'Logo','Converter':'ImageItem'}}"},
{this.Buttons, "{'ItemsSource':{'Path':'ButtonItems'}}"},
{this.SlideMenu, "{'ItemsSource':{'Path':'VisibleViews'}}"},
{
this.SortingView,
"{'ItemsSource':{'Path':'CategoriesName'},'SelectedGroups':{'Path':'SelectedGroups'},'ForceUbracoUpdateAction':{'Path':'ForceUbracoUpdateAction'}}"
},
{this.SettingsButton, "{'TouchDown':{'Path':'TopRightButtonClick'},'Hide':{'Path':'HideTopbarButton'},'ImageData':{'Path':'TopButtonImage','Converter':'ImageItem'}}" },
{this.TopRightButton, "{'TouchDown':{'Path':'SecondaryButtonButtonPushed'},'Hide':{'Path':'HideTopbarButton2'},'ImageData':{'Path':'SettingsButtonImage','Converter':'ImageItem'}}" }
});
// Perform any additional setup after loading the view, typically from a nib.
NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.DidBecomeActiveNotification, ReEnableSlideMenu);
this.SortingView.Hidden=true;
ViewModel.SettingButtonEvent += HandleSettingButtonPushed;
}
Here is my custom control "ButtonRow
[Register("ButtonRow")]
public class ButtonRow : CustomListViewController
{
private int _width = 0;
private UIImage _backgroundImage = null;
public ButtonRow(IntPtr handle)
: base(handle)
{
_width = (int)this.Frame.Width;
UseImageAsIcon = false;
FontSize=0;
}
public bool UseImageAsIcon { get; set; }
public UIImage BackgroundImage
{
get { return _backgroundImage; }
set { _backgroundImage = value; }
}
public int FontSize
{
get;set;
}
private Action _onClickAction;
private int _spacing = 0;
public Action OnClick
{
get
{
return _onClickAction;
}
set
{
_onClickAction = value;
}
}
/// <summary>
/// The add views.
/// </summary>
/// custom implementation for adding views to the button row
protected override void AddViews()
{
if (ItemsSource == null)
{
Hidden = true;
return;
}
base.AddViews();
foreach (UIView uiView in Subviews)
{
uiView.RemoveFromSuperview();
}
if (ItemsSource.Count == 0)
{
Hidden = true;
return;
}
if (_backgroundImage != null)
{
var frame = new RectangleF(0, 0, Frame.Width, Frame.Height);
var background = new UIImageView(frame) { Image = _backgroundImage };
AddSubview(background);
}
_width = _width - ((ItemsSource.Count - 1) * Spacing);
var buttonWidth = (int)Math.Ceiling (((double)_width) / ItemsSource.Count);
int index = 0;
foreach (ViewItemModel item in ItemsSource)
{
// creating custom button with needed viewmodel, nib etc is loaded in the class constructor
var button = new ButtonWithLabel(item, OnClick);
if (FontSize > 0)
{
button.FontSize(FontSize);
}
if (UseImageAsIcon)
{
button.AddBindings(
new Dictionary<object, string>
{
{ button, "{'IconLabel':{'Path':'Title'},'TitleFontColor':{'Path':'TitleFontColor'}}" },
{ button.icon, "{'ImageData':{'Path':'ImageIcon','Converter':'ImageItem'}}" }
});
}
else
{
// bindings created between the button and its viewmodel
button.AddBindings(
new Dictionary<object, string>
{
{button, "{'Label':{'Path':'Title'},'TitleFontColor':{'Path':'TitleFontColor'},'BackgroundColor':{'Path':'BackgroundColor'}}" },
{button.Background, "{'ImageData':{'Path':'ImageIcon','Converter':'ImageItem'}}" }
});
button.icon.Hidden = true;
}
// new frame of button is set, as the number of buttons is dynamic
int x = index == 0 ? 0 : index * (buttonWidth + Spacing);
button.SetFrame(new RectangleF(x, 0, buttonWidth, Frame.Height));
// the view of the button is added to the buttonrow view
AddSubview(button.View);
index++;
}
}
public int Spacing
{
get
{
return this._spacing;
}
set
{
this._spacing = value;
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
public override void Cleanup()
{
if(Subviews!=null)
{
foreach (var view in Subviews)
{
view.RemoveFromSuperview();
}
}
if(_backgroundImage!=null)
{
_backgroundImage.Dispose();
}
ItemsSource = null;
}
}
Here is my CustomListViewController
public class CustomListViewController: UIView
{
private IList _itemsSource;
private CustomViewSource _viewSource;
public CustomListViewController(MvxShowViewModelRequest showRequest)
{
ShowRequest = showRequest;
}
protected CustomListViewController()
{
}
public CustomListViewController(IntPtr handle)
: base(handle)
{
}
public bool IsVisible
{
get
{
return this.IsVisible;
}
}
public IList ItemsSource
{
get { return _itemsSource; }
set { _itemsSource = value; if(value!=null){CreateViewSource(_itemsSource); }}
}
public virtual void CreateViewSource(IList items)
{
if (_viewSource == null)
{
_viewSource = new CustomViewSource();
_viewSource.OnNewViewsReady += FillViews;
}
_viewSource.ItemsSource = items;
}
private void FillViews(object sender, EventArgs e)
{
AddViews();
}
protected virtual void AddViews()
{
// get views from source and do custom allignment
}
public virtual void Cleanup()
{
if(_viewSource!=null)
{
_itemsSource.Clear();
_itemsSource=null;
_viewSource.OnNewViewsReady -= FillViews;
_viewSource.ItemsSource.Clear();
_viewSource.ItemsSource = null;
_viewSource=null;
}
}
public MvxShowViewModelRequest ShowRequest { get;
private set;
}
}
And My CustomViewSource
public class CustomViewSource
{
private IList _itemsSource;
private List<UIView> _views=new List<UIView>();
public event EventHandler<EventArgs> OnNewViewsReady;
public CustomViewSource ()
{
}
public List<UIView> Views { get { return _views; } }
public virtual IList ItemsSource
{
get { return _itemsSource; }
set
{
// if (_itemsSource == value)
// return;
var collectionChanged = _itemsSource as INotifyCollectionChanged;
if (collectionChanged != null)
collectionChanged.CollectionChanged -= CollectionChangedOnCollectionChanged;
_itemsSource = value;
collectionChanged = _itemsSource as INotifyCollectionChanged;
if (collectionChanged != null)
collectionChanged.CollectionChanged += CollectionChangedOnCollectionChanged;
ReloadViewData();
}
}
protected object GetItemAt(int position)
{
if (ItemsSource == null)
return null;
return ItemsSource[position];
}
protected void CollectionChangedOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
ReloadViewData();
}
protected virtual void ReloadViewData()
{
if(ItemsSource==null){return;}
foreach (var VARIABLE in ItemsSource)
{
//call create view and add it to Views
}
//event new views created
if(OnNewViewsReady!=null)
OnNewViewsReady(this,new EventArgs());
}
protected virtual UIView CreateUIView(int position)
{
UIView view = null;
/*
//create view from nib
UIView newView=null;
return newView;
* */
return view;
}
}
Any one have any clues on how to make this work in mvvmcross v3 ?
I would like to make it so i can add x number of buttons and load the buttons from nib files. Have looked at the Kittens collection view example, but have not figured out how to make it work for my buttonRow, not sure if the collectionView is the right one to use as base.
The most common way to show a list is to use a UITableView.
There are quite a few samples around that show how to load these in MvvmCross:
n=2 and n=2.5 in http://mvvmcross.wordpress.com/
n=6 and n=6.5 in http://mvvmcross.wordpress.com/
Working with Collections in https://github.com/MvvmCross/MvvmCross-Tutorials/tree/master/Working%20With%20Collections
In your case, it looks like the previous coder has implemented some sort of custom control with custom layout. Adapting this to v3 shouldn't be particularly difficult, but you are going to need to read through and understand the previous code and how it works (this is nothing to do with MvvmCross - it's just app UI code).
One sample on dealing with custom views like this in iOS is the N=32 tutorial - http://slodge.blogspot.co.uk/2013/06/n32-truth-about-viewmodels-starring.html
Related
I am facing the issue with my Recycler view while placing Admob ads after every 5 items. First ads display after 5 items but when swept up/down the ads display random positions. What should I change to display ads after every 5 items?
AdapterFile:
public class TQuoteAdapter extends RecyclerView.Adapter<TQuoteAdapter.ImageHolder> {
private int ad_count = 0;
List<QuotesModel> quotesList;
Context context;
int width;
DBHelper_dbfile dbHelper;
int[] grid_colors;
int c = 0;
public TQuoteAdapter(List<QuotesModel> quotesList, Context context) {
this.quotesList = quotesList;
this.context = context;
dbHelper = new DBHelper_dbfile(context);
try {
dbHelper.openDatabase();
} catch (SQLException sqle) {
throw sqle;
}
DisplayMetrics displayMetrics = context.getResources()
.getDisplayMetrics();
width = displayMetrics.widthPixels;
grid_colors = context.getResources().getIntArray(R.array.grid_colors);
}
public static class ImageHolder extends RecyclerView.ViewHolder {
private NativeAdView nativeAdView;
ImageView favIV;
TextView quoteTV;
LinearLayout share, copy, wapp,fbapp,instaapp;
CardView cardView;
public ImageHolder(#NonNull View itemView) {
super(itemView);
nativeAdView = itemView.findViewById(R.id.native_ad_view);
quoteTV = itemView.findViewById(R.id.quoteTV);
favIV = itemView.findViewById(R.id.favIV);
share = itemView.findViewById(R.id.share);
copy = itemView.findViewById(R.id.copy);
wapp = itemView.findViewById(R.id.wapp);
fbapp = itemView.findViewById(R.id.fbapp);
instaapp = itemView.findViewById(R.id.instaapp);
cardView = itemView.findViewById(R.id.cardView);
}
}
#NonNull
#Override
public ImageHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_tquote, parent, false);
return new ImageHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull ImageHolder holder, int position) {
if(ad_count == 5){
AdLoader adLoader = new AdLoader.Builder(context,context.getString(R.string.native_unit_id))
.forNativeAd(new NativeAd.OnNativeAdLoadedListener() {
#Override
public void onNativeAdLoaded(#NonNull NativeAd nativeAd) {
populateNativeAdView(nativeAd,holder.nativeAdView);
if (onDestroy()) {
nativeAd.destroy();
}
}
})
.withAdListener(new AdListener() {
#Override
public void onAdFailedToLoad(LoadAdError adError) {
// Handle the failure by logging, altering the UI, and so on.
}
})
.withNativeAdOptions(new NativeAdOptions.Builder()
// Methods in the NativeAdOptions.Builder class can be
// used here to specify individual options settings.
.build())
.build();
adLoader.loadAd(new AdRequest.Builder().build());
ad_count = 0;
}else {
holder.quoteTV.setText(quotesList.get(position).getQuotes());
ad_count ++;
}
setAnimation(holder.itemView);
if (quotesList.get(position).getFavourite().equals("no")) {
holder.favIV.setImageResource(R.drawable.unpress_download);
} else {
holder.favIV.setImageResource(R.drawable.press_download);
}
holder.favIV.setOnClickListener(v -> {
if (Utills.hasPermissions(context, Utills.permissions)) {
ActivityCompat.requestPermissions((Activity) context, Utills.permissions, Utills.perRequest);
} else {
if (quotesList.get(position).getFavourite().equals("no")) {
dbHelper.addToFavourite("yes", quotesList.get(position).getId());
quotesList.get(position).setFavourite("yes");
} else {
dbHelper.addToFavourite("no", quotesList.get(position).getId());
quotesList.get(position).setFavourite("no");
}
notifyDataSetChanged();
}
});
int color = grid_colors[c];
c++;
if (c == grid_colors.length) {
c = 0;
}
holder.cardView.setCardBackgroundColor(color);
}
private void setAnimation(View view){
Animation animation = AnimationUtils.loadAnimation(context, android.R.anim.slide_in_left);
view.setAnimation(animation);
}
protected boolean onDestroy() {
AdManager.destroyFbAd();
return false;
}
#Override
public int getItemCount() {
return quotesList.size();
}
private void populateNativeAdView(NativeAd nativeAd, NativeAdView nativeAdView) {
nativeAdView.setMediaView((MediaView) nativeAdView.findViewById(R.id.media_view));
nativeAdView.setCallToActionView(nativeAdView.findViewById(R.id.cta));
nativeAdView.setHeadlineView(nativeAdView.findViewById(R.id.primary));
nativeAdView.setAdvertiserView(nativeAdView.findViewById(R.id.secondary));
nativeAdView.setStarRatingView(nativeAdView.findViewById(R.id.rating_bar));
nativeAdView.setBodyView(nativeAdView.findViewById(R.id.body));
nativeAdView.setIconView(nativeAdView.findViewById(R.id.icon));
View headlineView = nativeAdView.getHeadlineView();
Objects.requireNonNull(headlineView);
((AppCompatTextView) headlineView).setText(nativeAd.getHeadline());
View bodyView = nativeAdView.getBodyView();
Objects.requireNonNull(bodyView);
((AppCompatTextView) bodyView).setText(nativeAd.getBody());
View callToActionView = nativeAdView.getCallToActionView();
Objects.requireNonNull(callToActionView);
((AppCompatButton) callToActionView).setText(nativeAd.getCallToAction());
MediaView mediaView = nativeAdView.getMediaView();
Objects.requireNonNull(mediaView);
MediaContent mediaContent = nativeAd.getMediaContent();
Objects.requireNonNull(mediaContent);
mediaView.setMediaContent(mediaContent);
if (nativeAd.getIcon() == null) {
View iconView = nativeAdView.getIconView();
Objects.requireNonNull(iconView);
iconView.setVisibility(View.INVISIBLE);
} else {
View iconView2 = nativeAdView.getIconView();
Objects.requireNonNull(iconView2);
((AppCompatImageView) iconView2).setImageDrawable(nativeAd.getIcon().getDrawable());
nativeAdView.getIconView().setVisibility(View.VISIBLE);
}
if (nativeAd.getStarRating() == null) {
View starRatingView = nativeAdView.getStarRatingView();
Objects.requireNonNull(starRatingView);
starRatingView.setVisibility(View.INVISIBLE);
} else {
View starRatingView2 = nativeAdView.getStarRatingView();
Objects.requireNonNull(starRatingView2);
((RatingBar) starRatingView2).setRating(nativeAd.getStarRating().floatValue());
nativeAdView.getStarRatingView().setVisibility(View.VISIBLE);
}
if (nativeAd.getAdvertiser() == null) {
View advertiserView = nativeAdView.getAdvertiserView();
Objects.requireNonNull(advertiserView);
advertiserView.setVisibility(View.INVISIBLE);
} else {
View advertiserView2 = nativeAdView.getAdvertiserView();
Objects.requireNonNull(advertiserView2);
((AppCompatTextView) advertiserView2).setText(nativeAd.getAdvertiser());
nativeAdView.getAdvertiserView().setVisibility(View.VISIBLE);
}
nativeAdView.setNativeAd(nativeAd);
nativeAdView.setVisibility(View.VISIBLE);
}
I'm trying make a Custom View in Xamarin Forms that translates to an UICollectionView in IOS.
This first thing is fairly simple to do:
View:
public class CollectionView : View
{
}
Renderer:
public class CollectionViewRenderer : ViewRenderer<CollectionView, UICollectionView>
{
protected override void OnElementChanged(ElementChangedEventArgs<CollectionView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
SetNativeControl(new UICollectionView(new CGRect(0, 0, 200, 200), new UICollectionViewFlowLayout()));
}
if (e.NewElement != null)
{
...
Control.Source = new CollectionViewSource(a, this);
Control.ReloadData();
}
}
}
Now I would like to feed this CollectionView with DataTemplates (from a DataTemplateSelector). But I'm unable to find a way to register the classes:
From the template you can do:
Template.CreateContent();
to get the UI element.
But how can I register it in the collectionView for dequeue'ing in the CollectionSource
E.G.:
CollectionView.RegisterClassForCell(typeof(????), "CellId");
Hope, it will help you!!!
CustomControl
GridCollectionView.cs
using System;
using CoreGraphics;
using Foundation;
using UIKit;
namespace MyApp.Forms.Controls
{
public class GridCollectionView : UICollectionView
{
public GridCollectionView () : this (default(CGRect))
{
}
public GridCollectionView(CGRect frm)
: base(frm, new UICollectionViewFlowLayout())
{
AutoresizingMask = UIViewAutoresizing.All;
ContentMode = UIViewContentMode.ScaleToFill;
RegisterClassForCell(typeof(GridViewCell), new NSString (GridViewCell.Key));
}
public bool SelectionEnable
{
get;
set;
}
public double RowSpacing
{
get
{
return ((UICollectionViewFlowLayout)this.CollectionViewLayout).MinimumLineSpacing;
}
set
{
((UICollectionViewFlowLayout)this.CollectionViewLayout).MinimumLineSpacing = (nfloat)value;
}
}
public double ColumnSpacing
{
get
{
return ((UICollectionViewFlowLayout)this.CollectionViewLayout).MinimumInteritemSpacing;
}
set
{
((UICollectionViewFlowLayout)this.CollectionViewLayout).MinimumInteritemSpacing = (nfloat)value;
}
}
public CGSize ItemSize
{
get
{
return ((UICollectionViewFlowLayout)this.CollectionViewLayout).ItemSize;
}
set
{
((UICollectionViewFlowLayout)this.CollectionViewLayout).ItemSize = value;
}
}
public override UICollectionViewCell CellForItem(NSIndexPath indexPath)
{
if (indexPath == null)
{
//calling base.CellForItem(indexPath) when indexPath is null causes an exception.
//indexPath could be null in the following scenario:
// - GridView is configured to show 2 cells per row and there are 3 items in ItemsSource collection
// - you're trying to drag 4th cell (empty) like you're trying to scroll
return null;
}
return base.CellForItem(indexPath);
}
public override void Draw (CGRect rect)
{
this.CollectionViewLayout.InvalidateLayout ();
base.Draw (rect);
}
public override CGSize SizeThatFits(CGSize size)
{
return ItemSize;
}
}
}
Renderer Class
GridViewRenderer.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using MyApp.Controls;
using System.Collections.Generic;
[assembly: ExportRenderer (typeof(GridView), typeof(GridViewRenderer))]
namespace MyApp.Controls
{
public class GridViewRenderer: ViewRenderer<GridView,GridCollectionView>
{
private GridDataSource _dataSource;
public GridViewRenderer ()
{
}
public int RowsInSection(UICollectionView collectionView, nint section)
{
return ((ICollection) this.Element.ItemsSource).Count;
}
public void ItemSelected(UICollectionView tableView, NSIndexPath indexPath)
{
var item = this.Element.ItemsSource.Cast<object>().ElementAt(indexPath.Row);
this.Element.InvokeItemSelectedEvent(this, item);
}
public UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var item = this.Element.ItemsSource.Cast<object>().ElementAt(indexPath.Row);
var viewCellBinded = (this.Element.ItemTemplate.CreateContent() as ViewCell);
if (viewCellBinded == null) return null;
viewCellBinded.BindingContext = item;
return this.GetCell(collectionView, viewCellBinded, indexPath);
}
protected virtual UICollectionViewCell GetCell(UICollectionView collectionView, ViewCell item, NSIndexPath indexPath)
{
var collectionCell = collectionView.DequeueReusableCell(new NSString(GridViewCell.Key), indexPath) as GridViewCell;
if (collectionCell == null) return null;
collectionCell.ViewCell = item;
return collectionCell;
}
protected override void OnElementChanged (ElementChangedEventArgs<GridView> e)
{
base.OnElementChanged (e);
if (e.OldElement != null)
{
Unbind (e.OldElement);
}
if (e.NewElement != null)
{
if (Control == null)
{
var collectionView = new GridCollectionView() {
AllowsMultipleSelection = false,
SelectionEnable = e.NewElement.SelectionEnabled,
ContentInset = new UIEdgeInsets ((float)this.Element.Padding.Top, (float)this.Element.Padding.Left, (float)this.Element.Padding.Bottom, (float)this.Element.Padding.Right),
BackgroundColor = this.Element.BackgroundColor.ToUIColor (),
ItemSize = new CoreGraphics.CGSize ((float)this.Element.ItemWidth, (float)this.Element.ItemHeight),
RowSpacing = this.Element.RowSpacing,
ColumnSpacing = this.Element.ColumnSpacing
};
Bind (e.NewElement);
collectionView.Source = this.DataSource;
//collectionView.Delegate = this.GridViewDelegate;
SetNativeControl (collectionView);
}
}
}
private void Unbind (GridView oldElement)
{
if (oldElement == null) return;
oldElement.PropertyChanging -= this.ElementPropertyChanging;
oldElement.PropertyChanged -= this.ElementPropertyChanged;
var itemsSource = oldElement.ItemsSource as INotifyCollectionChanged;
if (itemsSource != null)
{
itemsSource.CollectionChanged -= this.DataCollectionChanged;
}
}
private void Bind (GridView newElement)
{
if (newElement == null) return;
newElement.PropertyChanging += this.ElementPropertyChanging;
newElement.PropertyChanged += this.ElementPropertyChanged;
var source = newElement.ItemsSource as INotifyCollectionChanged;
if (source != null)
{
source.CollectionChanged += this.DataCollectionChanged;
}
}
private void ElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == GridView.ItemsSourceProperty.PropertyName)
{
var newItemsSource = this.Element.ItemsSource as INotifyCollectionChanged;
if (newItemsSource != null)
{
newItemsSource.CollectionChanged += DataCollectionChanged;
this.Control.ReloadData();
}
}
else if(e.PropertyName == "ItemWidth" || e.PropertyName == "ItemHeight")
{
this.Control.ItemSize = new CoreGraphics.CGSize ((float)this.Element.ItemWidth, (float)this.Element.ItemHeight);
}
}
private void ElementPropertyChanging (object sender, PropertyChangingEventArgs e)
{
if (e.PropertyName == "ItemsSource")
{
var oldItemsSource = this.Element.ItemsSource as INotifyCollectionChanged;
if (oldItemsSource != null)
{
oldItemsSource.CollectionChanged -= DataCollectionChanged;
}
}
}
private void DataCollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
{
InvokeOnMainThread (()=> {
try
{
if(this.Control == null)
return;
this.Control.ReloadData();
// TODO: try to handle add or remove operations gracefully, just reload the whole collection for other changes
// InsertItems, DeleteItems or ReloadItems can cause
// *** Assertion failure in -[XLabs_Forms_Controls_GridCollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:],
// BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.30.14/UICollectionView.m:4324
// var indexes = new List<NSIndexPath>();
// switch (e.Action) {
// case NotifyCollectionChangedAction.Add:
// for (int i = 0; i < e.NewItems.Count; i++) {
// indexes.Add(NSIndexPath.FromRowSection((nint)(e.NewStartingIndex + i),0));
// }
// this.Control.InsertItems(indexes.ToArray());
// break;
// case NotifyCollectionChangedAction.Remove:
// for (int i = 0; i< e.OldItems.Count; i++) {
// indexes.Add(NSIndexPath.FromRowSection((nint)(e.OldStartingIndex + i),0));
// }
// this.Control.DeleteItems(indexes.ToArray());
// break;
// default:
// this.Control.ReloadData();
// break;
// }
}
catch { } // todo: determine why we are hiding a possible exception here
});
}
private GridDataSource DataSource
{
get
{
return _dataSource ?? (_dataSource = new GridDataSource (GetCell, RowsInSection,ItemSelected));
}
}
protected override void Dispose (bool disposing)
{
base.Dispose (disposing);
if (disposing && _dataSource != null)
{
Unbind (Element);
_dataSource.Dispose ();
_dataSource = null;
}
}
}
}
For more information Click here for custom class and click here for renderer class
I have created a custom control deriving from Button in WPF. I have overriden DefaultStyleKeyProperty and then set dependency property for Content. This control has its own style for OnMouseEnter, OnMouseLeave , OnMouseDown and OnMouseUp. When used, the events and customized styles appear but content is not shown. How do i set the content?
The control's code is below:
public class SymbolButton : Button
{
public static readonly DependencyProperty ContentProperty;
static SymbolButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SymbolButton), new FrameworkPropertyMetadata(typeof(SymbolButton)));
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
metadata.AffectsArrange = true;
ContentProperty = DependencyProperty.Register("Content",
typeof(string), typeof(SymbolButton), new UIPropertyMetadata(null));
}
Point startPoint = new Point(0.5, 0);
Point endPoint = new Point(0.5, 1);
public string Content
{
get { return (string)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public SymbolButton()
{
//this.Content = "Symbol";
ButtonDefaultDisplay();
this.BorderThickness = new Thickness(3.3);
this.FontSize = 21;
this.FontStyle = FontStyles.Oblique;
}
private void ButtonDefaultDisplay()
{
this.Foreground = Brushes.BlueViolet;
this.Background = new LinearGradientBrush(Colors.LightBlue,
Colors.MediumAquamarine,
startPoint, endPoint);
this.BorderBrush = Brushes.DarkTurquoise;
}
private void ButtonClickDisplay()
{
this.Foreground = Brushes.LightCyan;
this.Background = new LinearGradientBrush(Colors.MidnightBlue,
Colors.DarkTurquoise,
startPoint, endPoint);
this.BorderBrush = Brushes.Cyan;
}
private void ButtonHighlight()
{
this.Foreground = Brushes.Red;
this.Background = new LinearGradientBrush(Colors.MistyRose,
Colors.LightBlue,
endPoint, startPoint);
this.BorderBrush = Brushes.Plum;
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
ButtonClickDisplay();
}
protected override void OnMouseUp(MouseButtonEventArgs e)
{
ButtonHighlight();
}
protected override void OnMouseEnter(MouseEventArgs e)
{
ButtonHighlight();
}
protected override void OnMouseLeave(MouseEventArgs e)
{
ButtonDefaultDisplay();
}
}
Am a little stuck with getting changes reflected from the ViewModel to the View when used in a MvxBindableTableViewCell. I am using the vNext branch of MvvmCross on iOS.
Everything is set up properly and the initial values are visible when loading/showing the list for the first time. The list is a ObservableCollection<T> and the ViewModels inherit from MvxViewModel (thus implements INotifyPropertyChanged).
The main ViewModel looks like this:
public abstract class BaseViewModel : MvxViewModel, IMvxServiceConsumer
{
//... just regular implementation
}
public class UploadListViewModel: BaseViewModel
{
private readonly IUploadItemTasks uploadItemTasks;
private readonly IPhotoPickerService photoPickerService;
public IObservableCollection<UploadItemViewModel> Uploads { get { return this.LoadUploadItems(); } }
public UploadListViewModel()
{
this.uploadItemTasks = this.GetService<IUploadItemTasks>();
this.photoPickerService = this.GetService<IPhotoPickerService>();
}
private IObservableCollection<UploadItemViewModel> LoadUploadItems()
{
using (var unitOfWork = UnitOfWork.Start ())
{
return new SimpleObservableCollection<UploadItemViewModel>(uploadItemTasks.GetAll());
}
}
public void StartUpload ()
{
if (this.Uploads == null || this.Uploads.Count == 0) {
ReportError("Error", "No images to upload");
return;
}
this.Uploads.ForEach (uploadItem => PostCallback (uploadItem));
}
private void PostCallback (UploadItemViewModel uploadAsset)
{
IProgressReporter progressReporter = uploadAsset;
this.photoPickerService.GetAssetFullImage(uploadAsset.ImageUrl,
(image) => {
UIImage fullImage = image;
NSData jpeg = fullImage.AsJPEG();
byte[] jpegBytes = new byte[jpeg.Length];
System.Runtime.InteropServices.Marshal.Copy(jpeg.Bytes, jpegBytes, 0, Convert.ToInt32(jpeg.Length));
MemoryStream stream = new MemoryStream(jpegBytes);
Uri destinationUrl = new Uri(uploadAsset.DestinationUrl + "&name=" + uploadAsset.Name + "&contentType=image%2FJPEG");
//TO DO: Move this to plugin
var uploader = new Uploader().UploadPicture (destinationUrl, stream, UploadComplete, progressReporter);
uploader.Host = uploadAsset.Host;
ThreadPool.QueueUserWorkItem (delegate {
uploader.Upload ();
jpeg = null;
});
});
}
private void UploadComplete (string name)
{
if (name == null){
ReportError("Error","There was an error uploading the media.");
} else
{
//ReportError("Succes", name);
}
}
The item ViewModel looks like:
public interface IProgressReporter
{
float Progress { get; set;}
}
public abstract class BaseAssetViewModel: BaseViewModel, IBaseAssetViewModel
{
//... just regular properties
}
public class UploadItemViewModel: BaseAssetViewModel, IProgressReporter
{
public UploadItemViewModel(): base()
{
}
private float progress;
public float Progress {
get {
return this.progress;
}
set {
this.progress = value;
this.RaisePropertyChanged(() => Progress);
}
}
}
The View for the items inherits from MvxBindableTableViewCell and has the property:
private float progress;
public float ProgressMarker {
get {
return progress;
}
set {
progress = value;
// change progressbar or textfield here
}
}
The tableviewcell is bounded to the UploadItemViewModel via the BindingText:
public const string BindingText = #"ProgressMarker Progress, Converter=Float;";
The Uploader class mentioned in the snippet of UploadListViewModel implements a private method which tries to set the progress on the IProgressReporter.
float progressValue;
void SetProgress (float newvalue)
{
progressValue = newvalue;
this.dispatcher.InvokeOnMainThread (delegate {
if (ProgressReporter != null)
ProgressReporter.Progress = progressValue;
});
}
During the first viewing of the list I can see that the properties in both the ViewModel and View are being hit but when I update the ViewModel via the interface IProgressReporter with a new value in Progress the View in the tableviewcell is not updated nor the property is being called.
What am I doing wrong or what am I missing here?
UPDATE: Check the answer to this question.
I found why the binding didn't work. I was replacing the ObservableCollection over and over again.. I changed that piece of code as stated below and now it reflects the changes made to the UploadItemViewModel in the View of the cell.
private IObservableCollection<UploadItemViewModel> uploads;
private IObservableCollection<UploadItemViewModel> LoadUploadItems()
{
if (uploads == null)
{
using (var unitOfWork = UnitOfWork.Start ())
{
uploads = new SimpleObservableCollection<UploadItemViewModel>(uploadItemTasks.FindAll());
}
}
return uploads;
}
I'm trying to use the Three20 TTPhotoViewController with MonoTouch. I've derived FacebookPhoto from TTPhoto and FacebookPhotoSource from TTPhotoSource and am now trying to invoke the TTPhotoViewController but I get the following exception when pushing the view controller:
Objective-C exception thrown. Name: NSInvalidArgumentException Reason: * -[NSPlaceholderString initWithFormat:locale:arguments:]: nil argument
I noticed that the monotouch bindings in this github project: https://github.com/mono/monotouch-bindings/tree/492f68c3c2007f0638452cc8a5a762556db224ba/Three20/binding were missing the photoAtIndex binding, so I added that and recompiled them, but I haven't been able to figure out why I am getting this exception.
Here is how I'm invoking the TTPhotoViewController:
List<Photo> photoList = FacebookGraphApi.Instance.GetAlbumPhotos(album.id);
List<FacebookPhoto> fbPhotoList = photoList.Select(x => new FacebookPhoto(x)).ToList();
var photos = new TTPhotoViewController();
photos.PhotoSource = new FacebookPhotoSource(fbPhotoList);
NavController.PushViewController(photos, true);
Here is the definition of the TTPhotoSource
class FacebookPhotoSource : TTPhotoSource
{
List<FacebookPhoto> _photoList;
public FacebookPhotoSource (List<FacebookPhoto> photoList)
{
_photoList = photoList;
int i = 0;
foreach (FacebookPhoto photo in photoList) {
photo.PhotoSource = this;
photo.Index = i++;
}
}
public override string Title {
get {
return "Facebook Photos";
}
set {
throw new NotImplementedException();
}
}
public override int NumberOfPhotos {
get {
return _photoList.Count;
}
}
public override int MaxPhotoIndex {
get {
return _photoList.Count -1;
}
}
public override TTPhoto PhotoAtIndex(int photoIndex)
{
return _photoList[photoIndex];
}
}
and here is the definition of the FacebookPhoto:
class FacebookPhoto : TTPhoto
{
Photo _photo;
public FacebookPhoto(Photo photo)
{
_photo = photo;
}
public override string Caption {
get {
if(_photo.name == null)
return "";
return _photo.name;
}
set {
throw new NotImplementedException();
}
}
public override TTPhotoSource PhotoSource { get; set; }
public override int Index { get; set; }
public override SizeF Size {
get {
return new SizeF(_photo.width, _photo.height);
}
set {
throw new NotImplementedException();
}
}
public override string URLForVersion (int version)
{
switch (version) {
case 4:
return _photo.picture;
default:
return _photo.source;
}
}
}