I have a MvxBaseBindableCollectionViewCell which loads a xib that contains a custom button. I would like to be able to pass this custom button a ViewModel to bind to. Is this possible?
I'm trying to acheive something like MyButton.ViewModel = ViewModel.ChildViewModel and have ViewModel.ChildViewModel.Name show as the button title.
If you want to custom bind a cell, then there's a tutorial on this in http://slodge.blogspot.co.uk/2013/01/uitableviewcell-using-xib-editor.html
If you want to create a fully bindable UIButton within that View then you can do this using some inheritance like:
[Register("MyButton")]
public class MyButton
: UIButton
, IMvxServiceConsumer
{
private IList<IMvxUpdateableBinding> _bindings;
private const string BindingText = "SpecialTitle Customer.Name";
public MyButton()
{
}
public MyButton(IntPtr handle)
: base(handle)
{
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
foreach (var binding in _bindings)
{
binding.Dispose();
}
_bindings.Clear();
}
base.Dispose(disposing);
}
private object _dc;
public object DataContext
{
get { return _dc; }
set
{
_dc = value;
if (_bindings == null)
{
var binder = this.GetService<IMvxBinder>();
_bindings = binder.Bind(_dc, this, BindingText).ToList();
}
else
{
foreach (var binding in _bindings)
{
binding.DataContext = _dc;
}
}
}
}
public string SpecialTitle
{
get { return this.GetTitle(UIControlState.Normal); }
set { this.SetTitle(value, UIControlState.Normal); }
}
}
Aside> MvvmCross v3 "Hot Tuna" will contain some helper classes to make this a bit simpler to do.
Related
I'm writing a C# Windows form program and I'm having a hard time with PropertyGrids and UITypeEditor... I've read this post already: How to create custom PropertyGrid editor item which opens a form? which helped me getting started.
Here's the basic code I've got so far:
public FooClass Foo { get; private set; }
[Editor(typeof(FooEditor), typeof(UITypeEditor)),
TypeConverter(typeof(FooType))]
public class FooClass
{
private bool _enabled;
public bool Enabled
{
get { return _enabled; }
set { _enabled = value; }
}
public void ShowWindow()
{
using (var test = new Form())
{
test.ShowDialog();
}
}
}
private class FooType : ExpandableObjectConverter
{
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return "Click on the button to show the window";
}
}
private class FooEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
var svc = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
var foo = value as FooClass;
if (svc != null && foo != null)
{
foo.ShowWindow();
}
return null;
}
}
That works as expected: when the Foo object is selected in the propertygrid I see the "..." button on the right, I click and the window shows up:
What I can't figure out is how to make this "..." button stay visible even when the Foo object is not selected in the propertygrid:
Dis I miss something simple?
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
I have a ViewModel called LocationsViewModel, in which I have a ObservableCollection<LocationViewModel>. Additionally I have a LocationsView, which is an MvxCollectionViewController, in which I create a binding set and bind a MvxCollectionViewSource to the ObservableCollection.
In the LocationCell, which is a MvxCollectionViewCell, I want to display a MonoTouch.Dialog which is bound to various properties in the currently
selected LocationViewModel. The easiest way seems to be to create a nested MvxDialogViewController in the MvxCollectionViewCell, however to bind the
Elements in the MvxDialogViewController, I obviously need to create a Binding Target. My question is really can I pass a binding target from the MvxCollectionViewCell to the MvxDialogViewController?
Let me also try to explain it briefly with some code to improve the understanding.
LocationsViewModel.cs
public class LocationsViewModel : MvxViewModel
{
...
public ObservableCollection<LocationViewModel> Locations
{
get { return _locationDataService.Locations.Locations; }
}
...
}
LocationViewModel.cs
public class LocationViewModel : MvxNotifyPropertyChanged
{
...
//Tons of public properties like:
public string Name
{
get { return LinkedDataModel.Name; }
set
{
LinkedDataModel.Name = value;
RaisePropertyChanged(() => Name);
}
}
public double CurrentNoiseLevel
{
get { return LinkedDataModel.CurrentNoiseLevel; }
set
{
LinkedDataModel.CurrentNoiseLevel = value;
RaisePropertyChanged(() => CurrentNoiseLevel);
}
}
...
}
LocationsView.cs
[Register("LocationView")]
public class LocationsView
: MvxCollectionViewController
{
static readonly NSString LocationCellId = new NSString("LocationCell");
private readonly bool _isInitialized;
public LocationsView()
: base(new UICollectionViewFlowLayout()
{
MinimumInteritemSpacing = 0f,
ScrollDirection = UICollectionViewScrollDirection.Horizontal,
MinimumLineSpacing = 0f
})
{
_isInitialized = true;
ViewDidLoad();
}
public new LocationsViewModel ViewModel
{
get { return (LocationsViewModel)base.ViewModel; }
set { base.ViewModel = value; }
}
public sealed override void ViewDidLoad()
{
if (!_isInitialized)
return;
base.ViewDidLoad();
CollectionView.RegisterClassForCell(typeof(LocationCell), LocationCellId);
var source = new MvxCollectionViewSource(CollectionView, LocationCellId);
CollectionView.Source = source;
CollectionView.PagingEnabled = true;
var set = this.CreateBindingSet<LocationsView, LocationsViewModel>();
set.Bind(source).To(vm => vm.Locations);
set.Apply();
CollectionView.ReloadData();
}
}
LocationCell.cs
public class LocationCell : MvxCollectionViewCell
{
[Export("initWithFrame:")]
public LocationCell(RectangleF frame)
: base(string.Empty, frame)
{
InitView();
}
public LocationCell(IntPtr handle)
: base(string.Empty, handle)
{
InitView();
}
private void InitView()
{
var cell = new LocationCellDialog();
ContentView.Add(cell.View);
}
public class LocationCellDialog
: MvxDialogViewController
{
public LocationCellDialog()
: base(UITableViewStyle.Grouped, null, true)
{ }
public override void ViewDidLoad()
{
base.ViewDidLoad();
//How do I get the target here?
var target = ??;
Root = new RootElement
{
new Section
{
new StringElement().Bind(target, t => t.Name),
new StringElement().Bind(target, t => t.CurrentNoiseLevel)
}.Bind(target, t => t.Name),
};
}
}
}
So the question is can I simply pass along a binding target from the parent LocationCell to nested LocationCellDialog or is that a no go?
Each bindable view in MvvmCross has its own DataContext
For a top level View this DataContext is the ViewModel
For a Cell within a List, Table or Collection then the DataContext is set to the object in the list which the Cell is currently showing.
If you want to data-bind any property within a Cell to a property path on the DataContext then you can do so using the Fluent binding syntax.
For example, to bind the Text value of a child UILabel called myLabel to a child property Name on a Person in the list you could use:
this.CreateBinding(myLabel).For(label => label.Text).To<Person>(p => p.Name).Apply();
Or if you wanted to bind the Text to the Person itself you could use:
this.CreateBinding(myLabel).For(label => label.Text).Apply();
In your LocationCell I think you are saying you want to bind the DataContext of the nested LocationCellDialog to the DataContext of the containing cell.
To do this you should be able to use:
private void InitView()
{
var cell = new LocationCellDialog();
ContentView.Add(cell.View);
this.CreateBinding(cell).For(cell => cell.DataContext).Apply();
}
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 working with Sharepoint, and I try to connect web-parts with multiple parameters.
My question is how do I pass more than one parameter from a custome web part to another.
I am able to pass one parameter by implementing the ITransformableFilterValues interface in the custom webpart , what I want to do is pass more than one parameter (MyIndex2 for example).
// Configure interface
public bool AllowEmptyValue
{
get { return false; }
}
public bool AllowAllValue
{
get { return true; }
}
public bool AllowMultipleValues
{
get { return true; }
}
public string ParameterName
{
get { return "MyIndex"; } // Name of provided parameter
}
public ReadOnlyCollection<string> ParameterValues
{
get
{
EnsureChildControls();
List<string> MyFilterValues = new List<string>();
if (MyFilterValue != null)
{
MyFilterValues.Add(MyFilterValue); //Provided value for another web-part
}
ReadOnlyCollection<string> result = new ReadOnlyCollection<string>(MyFilterValues);
return result;
}
}
[ConnectionProvider("MyIndex", "UniqueIDForRegionConnection", AllowsMultipleConnections = true)]
public ITransformableFilterValues SetConnection()
{
return this;
}
Thanks for help. And sorry for my English.
Create a class that implements the ITransformableFilterValues interface (rather than implementing it in your web part class)
class FilterValues : ITransformableFilterValues
{
...
}
In your main web part have
FilterValues _fitler1;
FitlerValues _filter2;
(obviously you will need to set them up too)
Add methods to return the different filters e.g.
[ConnectionProvider("Filter 1", "UniqueIDForFilter1",
AllowsMultipleConnections = true)]
public ITransformableFilterValues SetConnection()
{
return _fitler1;
}
[ConnectionProvider("Filter 2", "UniqueIDForFilter2",
AllowsMultipleConnections = true)]
public ITransformableFilterValues SetConnection2()
{
return _fitler2;
}
Make sure the report parameters are in Visible mode. It worked for me. Give it a try
Thanks
Vivek
To fix your code modify as below:
[aspnetwebparts.ConnectionProvider("Season", "idSeason", AllowsMultipleConnections = true)]
public wsswebparts.ITransformableFilterValues SetConnectionSeason()
{
return filterValuesSeason;
}
[aspnetwebparts.ConnectionProvider("Category", "idCategory", AllowsMultipleConnections = true)]
public wsswebparts.ITransformableFilterValues SetConnectionCategory()
{
return filterValuesCategory;
}
filterValuesYear = new FilterValues("Year", ddlYear.SelectedValue);
filterValuesQuarter = new FilterValues("Quarter", ddlQuarter.SelectedValue);
[ConnectionProvider("Year", "performance_year", AllowsMultipleConnections = true)]
public ITransformableFilterValues SetConnectionYear()
{
return filterValuesYear;
}
[ConnectionProvider("Quarter", "performance_monthly", AllowsMultipleConnections = true)]
public ITransformableFilterValues SetConnectionQuarter()
{
return filterValuesQuarter;
}
After setting up every thing WebPart Renders, i see Both Connection is available.
After trying to send Connection to ReportViewerWebPart Window opens and getting Null Ref (I tried creating object as when it is needed but its not working)
Thanks
-Samar