I have subclassed UITableView with the required methods overridden in the subclass to populate the table view. However while NumberOfSections is called, NumberOfRowsInSection is not called.
The code below is from a Xamarin project but this is no different to its equivalent native version with slight differences in method names.
partial class ShootsTable : UITableView, IUISearchResultsUpdating, IUISearchBarDelegate
{
public bool History { get; set; }
public UIViewController Parent { get; set; }
private Bootleg.API.Event[] Events { get; set; }
private List<nint> ExpandedSections { get; }
public ShootsTable (IntPtr handle) : base (handle)
{
ExpandedSections = new List<nint> ();
SetEvents();
}
private void SetEvents()
{
if (History) {
Events = AppDelegate.Api.GetShootHistory ().ToArray();
} else {
Events = AppDelegate.Api.MyEvents.ToArray();
}
}
private void AddRefreshControl()
{
var refreshControl = new UIRefreshControl ();
refreshControl.AttributedTitle = new NSAttributedString ("Pull to refresh");
refreshControl.AddTarget (async delegate(object sender, EventArgs e) {
await AppDelegate.Api.RefreshEvents();
SetEvents();
ReloadData();
refreshControl.EndRefreshing();
}, UIControlEvent.ValueChanged);
AddSubview (refreshControl);
}
private void AddSearchControl()
{
var searchControl = new UISearchController (Parent);
searchControl.SearchResultsUpdater = this;
searchControl.SearchBar.Delegate = this;
TableHeaderView = searchControl.SearchBar;
}
public void UpdateSearchResultsForSearchController (UISearchController searchController)
{
SetEvents ();
Events = Events.Where (e => e.name.Contains (searchController.SearchBar.Text)).ToArray ();
ReloadData ();
}
public override nint NumberOfSections ()
{
if (Events != null && Events.Length > 0)
{
SeparatorStyle = UITableViewCellSeparatorStyle.SingleLine;
return Events.Length;
}
var label = new UILabel (new CGRect (Bounds.X, Bounds.Y, Bounds.Size.Width, Bounds.Size.Height));
label.Text = History ? "You have not contributed to any shoots yet." : "No shoots available.";
label.Lines = 2;
label.TextAlignment = UITextAlignment.Center;
BackgroundView = label;
SeparatorStyle = UITableViewCellSeparatorStyle.None;
return 0;
}
public override nint NumberOfRowsInSection (nint section)
{
var e = Events[section];
if (e.group == null)
{
return 1;
}
return ExpandedSections.Contains (section) ? e.events.Count : 0;
}
[Export ("tableView:heightForHeaderInSection:")]
public System.nfloat GetHeightForHeader (UIKit.UITableView tableView, System.nint section)
{
return Events [section].group == null ? 0 : tableView.SectionHeaderHeight;
}
[Export ("tableView:viewForHeaderInSection:")]
public UIKit.UIView GetViewForHeader (UIKit.UITableView tableView, System.nint section)
{
var e = Events [section];
if (e.group == null)
{
return null;
}
var cell = (ShootGroupCell) tableView.DequeueReusableCell (ShootGroupCell.Key);
cell.Event = e;
cell.AddGestureRecognizer (new UITapGestureRecognizer(() => {
if (ExpandedSections.Contains(section))
{
ExpandedSections.Remove(section);
tableView.ReloadSections(new NSIndexSet((nuint) section), UITableViewRowAnimation.Automatic);
} else {
ExpandedSections.Add(section);
tableView.ReloadSections(new NSIndexSet((nuint) section), UITableViewRowAnimation.Automatic);
}
}));
return cell.ContentView;
}
public override UITableViewCell CellAt (NSIndexPath ns)
{
var e = Events[ns.Section];
e = e.group == null ? e : e.events[ns.Row];
if (History) {
var cell = (ShootCell)DequeueReusableCell (ShootCell.Key);
cell.Event = e;
cell.Parent = Parent;
return cell;
} else {
var cell = (ShootJoinCell) DequeueReusableCell (ShootJoinCell.Key);
cell.Event = e;
return cell;
}
}
[Export ("tableView:heightForRowAtIndexPath:")]
public System.nfloat GetHeightForRow (UIKit.UITableView tableView, Foundation.NSIndexPath indexPath)
{
return Events [indexPath.Section].group == null || ExpandedSections.Contains (indexPath.Section) ? tableView.RowHeight : 0;
}
[Export ("tableView:didSelectRowAtIndexPath:")]
public async void RowSelected (UIKit.UITableView tableView, Foundation.NSIndexPath indexPath)
{
if (!History)
{
var e = Events [indexPath.Section];
if (e.group != null) {
e = e.events [indexPath.Row];
}
MBHUDView.HudWithBody ("Connecting Event...", MBAlertViewHUDType.ActivityIndicator, 0, true);
await AppDelegate.Api.ConnectToEvent (e, false);
MBHUDView.DismissCurrentHUD ();
if (AppDelegate.Api.CurrentEvent.id != e.id)
{
var alert = UIAlertController.Create ("Confirm", "This event requires you to confirm you wish to join.", UIAlertControllerStyle.Alert);
alert.AddAction(UIAlertAction.Create("Join", UIAlertActionStyle.Default, async delegate {
MBHUDView.HudWithBody ("Connecting Event...", MBAlertViewHUDType.ActivityIndicator, 0, true);
await AppDelegate.Api.ConnectToEvent (e, true);
MBHUDView.DismissCurrentHUD ();
e = AppDelegate.Api.CurrentEvent;
DialogHelper.PermissionsDialog (e, delegate {
Parent.PerformSegue("phasesSegue", tableView);
}, null, Parent);
}));
alert.AddAction(UIAlertAction.Create("Cancel", UIAlertActionStyle.Cancel, null));
Parent.PresentViewController (alert, true, null);
}
else
{
e = AppDelegate.Api.CurrentEvent;
DialogHelper.PermissionsDialog (e, delegate {
Parent.PerformSegue("phasesSegue", tableView);
}, null, Parent);
}
}
}
Now I have been looking at several possible solutions however it seems quite uncommon to sub-class UITableView with the majority of solutions simply being not setting the delegate or data source properly. I have also seen a SO solution to this issue simply being the TableView not being in view and therefore the delegate methods do not need to be called however this is not the case.
I do not need to set delegate methods as I am subclassing UITableView itself and it is sufficient to just override the built in methods. In the storyboard I set the class to my custom class ShootsTable.
Does anyone have any ideas?
Thanks in advance.
This appears to be a bug. Not sure what level (Xamarin or iOS) it is at but as soon as this is changed back to a IUITableViewDataSource implementation it works.
I will report this to Xamarin and see what comes back.
Related
I have a base class from System.Windows.Controls.Control that changes Visibility, Enabled , Background, Foreground properties according to data from outside.
when I use the class like below
public class RsdDesignBase : Button
{
....
}
It works for Button Control. I want to use same class for other controls like TextBox, Image, TextBlock but if I use like this I neet copy paste same code for all other controls.
Is there a way to use my RsdDesignBase class as base class for others controls ? Or any other way to do this.
I will paste whole class below. What it does is waits for changes in DataTag objects when they change it changes to some properties. For example if _enabledTag.Value is 0 it disables the control.
public class RsdDesignButtonBase : Button
{
private DataTag _visibilityTag;
private DataTag _enabledTag;
private DataTag _appearanceTag;
public TagScriptObject TagScriptObject { get; set; }
private readonly Timer _timer;
protected RsdDesignButtonBase()
{
Loaded += RSD_ButtonBase_Loaded;
Unloaded += OnUnloaded;
_timer = new Timer(1000);
_timer.Elapsed += TimerOnElapsed;
}
private void TimerOnElapsed(object sender, ElapsedEventArgs e)
{
Dispatcher.BeginInvoke(new Action(() =>
{
var background = Background;
var foreground = Foreground;
Background = foreground;
Foreground = background;
}), DispatcherPriority.Render);
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
if (_enabledTag != null) _enabledTag.DataChanged -= EnabledTagOnDataChanged;
if (_visibilityTag != null) _visibilityTag.DataChanged -= VisibilityTagOnDataChanged;
if (_appearanceTag != null) _appearanceTag.DataChanged -= AppearanceTagOnDataChanged;
}
private void RSD_ButtonBase_Loaded(object sender, RoutedEventArgs e)
{
DependencyPropertyDescriptor desc =
DependencyPropertyDescriptor.FromProperty(FrameworkElement.TagProperty, typeof(FrameworkElement));
desc.AddValueChanged(this, TagPropertyChanged);
TagPropertyChanged(null, null);
}
private void TagPropertyChanged(object sender, EventArgs e)
{
if (Tag == null) return;
TagScriptObject = JsonConvert.DeserializeObject<TagScriptObject>(Tag.ToString());
if (TagScriptObject?.VisibilityProperty?.TagId > 0)
{
_visibilityTag =
GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == TagScriptObject.VisibilityProperty?.TagId);
if (_visibilityTag != null)
{
_visibilityTag.DataChanged += VisibilityTagOnDataChanged;
VisibilityTagOnDataChanged(null, null);
}
}
if (TagScriptObject?.EnableProperty?.TagId > 0)
{
_enabledTag =
GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == TagScriptObject.EnableProperty?.TagId);
if (_enabledTag != null)
{
_enabledTag.DataChanged += EnabledTagOnDataChanged;
EnabledTagOnDataChanged(null, null);
}
}
if (TagScriptObject?.AppearanceProperty?.TagId > 0)
{
_appearanceTag =
GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == TagScriptObject.AppearanceProperty?.TagId);
if (_appearanceTag != null && !_appearanceTag.IsEventHandlerRegistered(null))
{
_appearanceTag.DataChanged += AppearanceTagOnDataChanged;
AppearanceTagOnDataChanged(null, null);
}
}
}
private void AppearanceTagOnDataChanged(object source, EventArgs args)
{
_timer.Enabled = false;
_ = Dispatcher.BeginInvoke(new Action(() =>
{
double tagValue;
bool result = true;
if (_appearanceTag.VarType == VarType.Bit)
{
tagValue = _appearanceTag.TagValue ? 1 : 0;
}
else
{
result = double.TryParse(_appearanceTag.TagValue.ToString(), out tagValue);
}
if (result)
{
foreach (var controlColor in TagScriptObject.AppearanceProperty.ControlColors)
{
if (tagValue >= controlColor.RangeMin &&
tagValue <= controlColor.RangeMax)
{
Background =
new BrushConverter().ConvertFromString(controlColor.Background) as SolidColorBrush;
Foreground =
new BrushConverter().ConvertFromString(controlColor.Foreground) as SolidColorBrush;
_timer.Enabled = controlColor.Flashing == ConfirmEnum.Yes;
break;
}
}
}
}), DispatcherPriority.Render);
}
private void EnabledTagOnDataChanged(object source, EventArgs args)
{
_ = Dispatcher.BeginInvoke(new Action(() =>
{
if (_enabledTag != null)
{
if (TagScriptObject.EnableProperty.IsRangeSelected)
{
double tagValue;
bool result = true;
if (_enabledTag.VarType == VarType.Bit)
{
tagValue = _enabledTag.TagValue ? 1 : 0;
}
else
{
result = double.TryParse(_enabledTag.TagValue.ToString(), out tagValue);
}
if (result)
{
if (tagValue >= TagScriptObject.EnableProperty.RangeFrom &&
tagValue <= TagScriptObject.EnableProperty.RangeTo)
{
IsEnabled = TagScriptObject.EnableProperty.DefaultValue;
}
else
{
IsEnabled = !TagScriptObject.EnableProperty.DefaultValue;
}
}
}
else
{
if (_enabledTag.IsNumeric || _enabledTag.VarType == VarType.Bit)
{
var bitArray = _enabledTag.GetBitArray();
var singleBit = TagScriptObject.EnableProperty.SingleBit;
if (bitArray.Count > singleBit)
{
if (bitArray[singleBit])
{
IsEnabled = TagScriptObject.EnableProperty.DefaultValue;
}
else
{
IsEnabled = !TagScriptObject.EnableProperty.DefaultValue;
}
}
}
}
}
}), DispatcherPriority.Render);
}
private void VisibilityTagOnDataChanged(object source, EventArgs args)
{
_ = Dispatcher.BeginInvoke(new Action(() =>
{
if (_visibilityTag != null)
{
if (TagScriptObject.VisibilityProperty.IsRangeSelected)
{
double tagValue;
bool result = true;
if (_visibilityTag.VarType == VarType.Bit)
{
tagValue = _visibilityTag.TagValue ? 1 : 0;
}
else
{
result = double.TryParse(_visibilityTag.TagValue.ToString(), out tagValue);
}
if (result)
{
if (tagValue >= TagScriptObject.VisibilityProperty.RangeFrom &&
tagValue <= TagScriptObject.VisibilityProperty.RangeTo)
{
Visibility = TagScriptObject.VisibilityProperty.DefaultValue
? Visibility.Visible
: Visibility.Hidden;
}
else
{
Visibility = TagScriptObject.VisibilityProperty.DefaultValue
? Visibility.Collapsed
: Visibility.Visible;
}
}
}
else
{
if (_visibilityTag.IsNumeric || _visibilityTag.VarType == VarType.Bit)
{
var bitArray = _visibilityTag.GetBitArray();
var singleBit = TagScriptObject.VisibilityProperty.SingleBit;
if (bitArray.Count > singleBit)
{
if (bitArray[singleBit])
{
Visibility = TagScriptObject.VisibilityProperty.DefaultValue
? Visibility.Visible
: Visibility.Hidden;
}
else
{
Visibility = TagScriptObject.VisibilityProperty.DefaultValue
? Visibility.Hidden
: Visibility.Visible;
}
}
}
}
}
}), DispatcherPriority.Render);
}
}
If I understand you correctly, you want to add some feature to Button, TextBox, Image and TextBlock (and possibly more) and reuse that code for all classes, right?
What you're doing right now is adding a Base at the bottom of the inheritance tree. That way you can't share it with other classes. Ideally, you would want to change the System.Windows.Controls.Control, but that's part of the .NET Framework, so you can't change that...
This is the downside of inheritance...
The only possibility I see is to use composition:
Create a class containing the logic you want. Let's call it RsdDesign. No superclass needed. It will look a lot like your RsdDesignButtonBase.
Create a descendant for every Control you want to add this feature to
Give those descendants a private member of type ``RsdDesign````.
Connect all applicable methods of the Control to the member.
public class RsdDesign
{
private DataTag _visibilityTag;
private DataTag _enabledTag;
private DataTag _appearanceTag;
public TagScriptObject TagScriptObject { get; set; }
private readonly Timer _timer;
private System.Windows.Controls.Control _parentControl
protected RsdDesign(System.Windows.Controls.Control parentControl)
{
_parentControl = parentControl;
_parentControl.Loaded += RSD_ButtonBase_Loaded;
_parentControl.Unloaded += OnUnloaded;
_timer = new Timer(1000);
_timer.Elapsed += TimerOnElapsed;
}
// The rest of your RsdDesignButtonBase implementation
// ...
}
public class RsdDesignButton: Button
{
private RsdDesign _design;
public RsdDesignButton(...)
{
_design = new RsdDesign(this);
}
// You may need to hook some of the methods explicitly like this:
private void EnabledTagOnDataChanged(object source, EventArgs args)
{
_design.EnabledTagOnDataChanged(source, args);
}
}
I haven't tried this, but maybe the idea helps you to find a solution.
If you derive from your RsdDesignButtonBase class from FrameworkElement:
public class RsdDesignBase : FrameworkElement
{
...
}
...you should be able to extend and customize it for TextBox, Image, TextBlock and any other FrameworkElement, e.g.:
public class TextBlock : RsdDesignBase {}
As far as I can see your control does two(three) things:
It sets a certain layout to the control (visibility, background etc)
it deals a lot with (de)serializing and processing JSON data.
Some of the processing in return modifies UI properties (e.g. Hide/Show) if certain data is available or not.
Following the helpful principal of "separation of concerns" - not because it sound academic or is 'awesome', but because you don't get into a mess of too tightly coupled code - I would much rather recommend to put all of this logic into an Attached Property or a set of Attached properties. And to pass the control as the first argument.
You would not have to change a lot of the implementation and you could use it for virtually all WPF elements that derive from Control or even FrameworkElement
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/attached-properties-overview?view=netframeworkdesktop-4.8
I'm trying to make an app for people of my student dancing association to find a dance partner. It's been a fun project during corona. Everything is done in ASP Core 5 and Xamarin Forms, and it's almost ready for testing. However, I'm having some trouble with the chat UI. When I use a CollectionView in Forms, it loads very slowly, due to rendering performance (at least on Android, iOS is generally much faster). To solve that I tried creating it as a view and use native renderers for iOS and Android. With Android, I simply used a NuGet package (XamarinLibrary.Xamarin.AndroidX.ChatKit), but for iOS I could find no such package. There's a native SwiftUI package (MessageKit), though I don't know how to wrap that (and the documentation is a lot). So I started to instead write it in Xamarin iOS. My test loads really quickly, although I'm now struggling with the layout of the Collection and the ViewCell. Could someone show me how to create message cells that fill the horizontal space?
Thanks in advance! :)
This is what it looks like right now:
Screenshot
[UPDATE] I partially solved the issue by adding this (though it does not work in the constructor):
Screenshot 2
public override bool ShouldInvalidateLayoutForBoundsChange(CGRect newBounds) {
MinimumLineSpacing = 10;
MinimumInteritemSpacing = 10;
SectionInset = new UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10);
ItemSize = new CGSize(width: (newBounds.Width - 20), height: 100);
return true;
}
My code currently (the data source isn't correct yet):
public class MessageListLayout : UICollectionViewFlowLayout {
public MessageListLayout() {
ItemSize = new CGSize(UIScreen.MainScreen.Bounds.Size.Width, 50);
EstimatedItemSize = AutomaticSize;
}
public override bool ShouldInvalidateLayoutForBoundsChange(CGRect newBounds) {
return true;
}
public override UICollectionViewLayoutAttributes LayoutAttributesForItem(NSIndexPath path) {
return base.LayoutAttributesForItem(path);
}
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect(CGRect rect) {
return base.LayoutAttributesForElementsInRect(rect);
}
}
public class MessageListDataSource : UICollectionViewDataSource {
private string CellId { get; set; }
#region Computed Properties
public UICollectionView CollectionView { get; set; }
public List<int> Numbers { get; set; } = new List<int>();
#endregion
#region Constructors
public MessageListDataSource(UICollectionView collectionView, string cellId) {
// Initialize
CollectionView = collectionView;
CellId = cellId;
// Init numbers collection
for (int n = 0; n < 100; ++n) {
Numbers.Add(n);
}
}
#endregion
#region Override Methods
public override nint NumberOfSections(UICollectionView collectionView) {
// We only have one section
return 1;
}
public override nint GetItemsCount(UICollectionView collectionView, nint section) {
// Return the number of items
return Numbers.Count;
}
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath) {
// Get a reusable cell and set {~~it's~>its~~} title from the item
var cell = collectionView.DequeueReusableCell(CellId, indexPath) as MessageListViewCell;
cell.Text = Numbers[(int)indexPath.Item].ToString();
return cell;
}
public override bool CanMoveItem(UICollectionView collectionView, NSIndexPath indexPath) {
// We can always move items
return true;
}
public override void MoveItem(UICollectionView collectionView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath) {
// Reorder our list of items
var item = Numbers[(int)sourceIndexPath.Item];
Numbers.RemoveAt((int)sourceIndexPath.Item);
Numbers.Insert((int)destinationIndexPath.Item, item);
}
#endregion
}
So this should be the base message bubble.
public class MessageListViewCell : UICollectionViewCell {
UIImageView imageView;
public string Text { get; set; }
[Export("initWithFrame:")]
public MessageListViewCell(CGRect frame) : base(frame) {
BackgroundView = new UIView { BackgroundColor = UIColor.Orange };
SelectedBackgroundView = new UIView { BackgroundColor = UIColor.Green };
ContentView.Layer.BorderColor = UIColor.LightGray.CGColor;
ContentView.Layer.BorderWidth = 2.0f;
ContentView.BackgroundColor = UIColor.White;
//ContentView.Transform = CGAffineTransform.MakeScale(1, 1);
ContentView.AutoresizingMask = UIViewAutoresizing.FlexibleWidth;
imageView = new UIImageView(UIImage.FromBundle("placeholder.png"));
imageView.Center = ContentView.Center;
imageView.Transform = CGAffineTransform.MakeScale(0.7f, 0.7f);
ContentView.AddSubview(imageView);
}
public UIImage Image {
set {
imageView.Image = value;
}
}
[Export("custom")]
public void Custom() {
// Put all your custom menu behavior code here
Console.WriteLine("custom in the cell");
}
public override bool CanPerform(Selector action, NSObject withSender) {
if (action == new Selector("custom"))
return true;
else
return false;
}
}
[assembly: ExportRenderer(typeof(MessagesListControl), typeof(MessagesListControlRenderer))]
namespace Vinder.iOS.UI.Renderers {
public class MessagesListControlRenderer : ViewRenderer<MessagesListControl, UICollectionView> {
static readonly NSString messageListViewCell = new("MessageListViewCell");
private UICollectionView collectionView { get; set; }
protected override void OnElementChanged(ElementChangedEventArgs<MessagesListControl> e) {
base.OnElementChanged(e);
if (e.OldElement != null) {
// Unsubscribe to events
}
if (e.NewElement != null) {
if (Control == null) {
collectionView = new UICollectionView(new CGRect(0, 0, UIScreen.MainScreen.Bounds.Size.Width, 300), new MessageListLayout());
collectionView.RegisterClassForCell(typeof(MessageListViewCell), messageListViewCell);
collectionView.BackgroundColor = UIColor.Blue; // blue is easy for seeing the correct bounds
collectionView.DataSource = new MessageListDataSource(collectionView, messageListViewCell);
SetNativeControl(collectionView);
}
// Subscribe to events
}
}
}
}
I'm trying to implement a pickview object instead of the keyboard on xamarin.ios, I found something on the net but they are not for me. I have to enter a numeric value, required on a TextField which when clicked instead of the standard keyboard will show my pickview. And it will insert it in the field to forward it afterwards to the php script for an insertion on a data base. I developed this code but I'm stuck, I humbly ask you for help :(
Please only c# code, if you would help me.
I display the Picker with my list but when I have to go to retrieve the data and insert it somewhere for example in the Textfield or in a variable to save it and send it to the PHP script I can't get the selected value!
public override void ViewDidLoad()
{
base.ViewDidLoad();
//gesture torna indietro
var BackGesture = new UISwipeGestureRecognizer(swipe);
BackGesture.Direction = UISwipeGestureRecognizerDirection.Right;
BackGesture.NumberOfTouchesRequired = 1;
View.AddGestureRecognizer(BackGesture);
//evento torna indietro
void swipe(UISwipeGestureRecognizer sw1)
{
//torno indietro
apriStoryBoard("Home");
}
var list = new List<string> { "1", "2" };
ConfigurePicker(list);
// picker.Model = colorsViewModel;
}
void ConfigurePicker(List<string> list)
{
var pickerTextField = new UITextField();
var picker = new UIPickerView();
picker.Model = new ColorsViewModel(list);
var screenWidth = UIScreen.MainScreen.Bounds.Width;
var pickerToolBar = new UIToolbar(new RectangleF(0, 0, (float)screenWidth, 44)) { BarStyle = UIBarStyle.Default, Translucent = true };
var flexibleSpaceButton = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
var doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, (sender, e) => Rips1.ResignFirstResponder());
pickerToolBar.SetItems(new[] { flexibleSpaceButton, doneButton }, false);
var colorsViewModel = new ColorsViewModel(list);
colorsViewModel.ColorChange += (sender, e) =>
{
Rips1.Text = colorsViewModel.SelectedColor;
};
Rips1.InputView = picker;
Rips1.InputAccessoryView = pickerToolBar;
}
PickviewModel class:
class ColorsViewModel : UIPickerViewModel
{
List<string> list;
public event EventHandler ColorChange;
public string SelectedColor { get; private set; }
public override System.nint GetRowsInComponent(UIPickerView pickerView, nint component) //non e uguale
{
return list.Count;
}
public ColorsViewModel(List<string> list)
{
this.list = list;
}
public override System.nint GetComponentCount(UIPickerView pickerView)
{
return 1;
}
public override string GetTitle(UIPickerView pickerView, nint row, nint component)
{
return list[(int)row];
}
//controla la selezione attuale
public override void Selected(UIPickerView pickerView, nint row, nint component)
{
var color = list[(int)row];
SelectedColor = color;
ColorChange?.Invoke(null, null);
}
public string getValore(nint row)
{
return list[(int)row];
}
}
I have using freshmvvm for my xamarin forms application. FreshTabbedNavigationContainer tabbed page is working fine with android. I have customized the android tabbed page font size, font color, image size. But in IOS I don't know how to change the tab bar from bottom to top like in android and how to change the size of the icon and font. Please anyone suggest me to done this. My tabbed page code is below,
var tabbedPage = new FreshTabbedNavigationContainer();
tabbedPage.AddTab<FirstPageModel>("One", "icon.png");
tabbedPage.AddTab<SecondPageModel>("Two", "icon.png");
await Application.Current.MainPage.Navigation.PushAsync(tabbedPage);
NavigationPage.SetHasNavigationBar(tabbedPage, false);
I have changed the tabbar scrollable using custom renderer for android like this,
public override void OnViewAdded(Android.Views.View child)
{
base.OnViewAdded(child);
var tabLayout = child as TabLayout;
if (tabLayout != null)
{
tabLayout.TabMode = TabLayout.ModeScrollable;
}
}
How to change the tab bar as scrollable for ios. In my tabbed page, the space between text and icon is zero. Please refer the screenshot.
Naxam's GitHub has sort of a similar implementation using a Customized version of Xamarin Forms TabbedPage, But since FreshTabbedNavigationContainer inherits from the same(TabbedPage) you can just use it instead and it should work like a charm.
public class TopTabbedPage : FreshTabbedNavigationContainer
{
public TopTabbedPage()
{
//BarBackgroundColor = Color.Blue;
//BarTextColor = Color.White;
}
public static readonly BindableProperty BarIndicatorColorProperty = BindableProperty.Create(
nameof(BarIndicatorColor),
typeof(Color),
typeof(TopTabbedPage),
Color.White,
BindingMode.OneWay);
public Color BarIndicatorColor
{
get { return (Color)GetValue(BarIndicatorColorProperty); }
set { SetValue(BarIndicatorColorProperty, value); }
}
public static readonly BindableProperty SwipeEnabledColorProperty = BindableProperty.Create(
nameof(SwipeEnabled),
typeof(bool),
typeof(TopTabbedPage),
true,
BindingMode.OneWay);
public bool SwipeEnabled
{
get { return (bool)GetValue(SwipeEnabledColorProperty); }
set { SetValue(SwipeEnabledColorProperty, value); }
}
}
And then the renderer would look something like this:
[assembly: ExportRenderer(typeof(TopTabbedPage), typeof(TopTabbedRenderer))]
namespace Naxam.Controls.Platform.iOS
{
using Platform = Xamarin.Forms.Platform.iOS.Platform;
using Forms = Xamarin.Forms.Forms;
public partial class TopTabbedRenderer :
UIViewController
{
public static void Init()
{
}
UIColor _defaultBarColor;
bool _defaultBarColorSet;
bool _loaded;
Size _queuedSize;
int lastSelectedIndex;
Page Page => Element as Page;
UIPageViewController pageViewController;
protected UIViewController SelectedViewController;
protected IList<UIViewController> ViewControllers;
protected IPageController PageController
{
get { return Page; }
}
protected TopTabbedPage Tabbed
{
get { return (TopTabbedPage)Element; }
}
protected TabsView TabBar;
private NSLayoutConstraint tabBarHeight;
public TopTabbedRenderer()
{
ViewControllers = new UIViewController[0];
pageViewController = new UIPageViewController(
UIPageViewControllerTransitionStyle.Scroll,
UIPageViewControllerNavigationOrientation.Horizontal,
UIPageViewControllerSpineLocation.None
);
TabBar = new TabsView
{
TranslatesAutoresizingMaskIntoConstraints = false
};
TabBar.TabsSelectionChanged += HandleTabsSelectionChanged;
}
public override void DidRotate(UIInterfaceOrientation fromInterfaceOrientation)
{
base.DidRotate(fromInterfaceOrientation);
View.SetNeedsLayout();
}
public override void ViewDidAppear(bool animated)
{
PageController.SendAppearing();
base.ViewDidAppear(animated);
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
PageController.SendDisappearing();
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
View.AddSubview(TabBar);
AddChildViewController(pageViewController);
View.AddSubview(pageViewController.View);
pageViewController.View.TranslatesAutoresizingMaskIntoConstraints = false;
pageViewController.DidMoveToParentViewController(this);
var views = NSDictionary.FromObjectsAndKeys(
new NSObject[] {
TabBar,
pageViewController.View
},
new NSObject[] {
(NSString) "tabbar",
(NSString) "content"
}
);
View.AddConstraints(NSLayoutConstraint.FromVisualFormat("V:|-0-[tabbar]-0-[content]-0-|",
0,
null,
views));
View.AddConstraints(NSLayoutConstraint.FromVisualFormat("H:|-0-[tabbar]-0-|",
0,
null,
views));
View.AddConstraints(NSLayoutConstraint.FromVisualFormat("H:|-0-[content]-0-|",
0,
null,
views));
tabBarHeight = NSLayoutConstraint.Create(
TabBar,
NSLayoutAttribute.Height,
NSLayoutRelation.Equal,
1, 68
);
TabBar.AddConstraint(tabBarHeight);
if (pageViewController.ViewControllers.Length == 0
&& lastSelectedIndex >= 0 || lastSelectedIndex < ViewControllers.Count)
{
pageViewController.SetViewControllers(
new[] { ViewControllers[lastSelectedIndex] },
UIPageViewControllerNavigationDirection.Forward,
true, null
);
}
UpdateSwipe(Tabbed.SwipeEnabled);
pageViewController.DidFinishAnimating += HandlePageViewControllerDidFinishAnimating;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
PageController?.SendDisappearing();
if (Tabbed != null)
{
Tabbed.PropertyChanged -= OnPropertyChanged;
Tabbed.PagesChanged -= OnPagesChanged;
TabBar.TabsSelectionChanged -= HandleTabsSelectionChanged;
}
if (pageViewController != null)
{
pageViewController.WeakDataSource = null;
pageViewController.DidFinishAnimating -= HandlePageViewControllerDidFinishAnimating;
pageViewController?.Dispose();
}
}
base.Dispose(disposing);
}
protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
{
ElementChanged?.Invoke(this, e);
}
UIViewController GetViewController(Page page)
{
var renderer = Platform.GetRenderer(page);
return renderer?.ViewController;
}
void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != Page.TitleProperty.PropertyName)
return;
if (!(sender is Page page) || page.Title is null)
return;
TabBar.ReplaceItem(page.Title, Tabbed.Children.IndexOf(page));
}
void OnPagesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
e.Apply((o, i, c) => SetupPage((Page)o, i), (o, i) => TeardownPage((Page)o, i), Reset);
SetControllers();
UIViewController controller = null;
if (Tabbed.CurrentPage != null)
{
controller = GetViewController(Tabbed.CurrentPage);
}
if (controller != null && controller != SelectedViewController)
{
SelectedViewController = controller;
var index = ViewControllers.IndexOf(SelectedViewController);
MoveToByIndex(index);
TabBar.SelectedIndex = index;
}
}
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(TabbedPage.CurrentPage))
{
var current = Tabbed.CurrentPage;
if (current == null)
return;
var controller = GetViewController(current);
if (controller == null)
return;
SelectedViewController = controller;
var index = ViewControllers.IndexOf(SelectedViewController);
MoveToByIndex(index);
TabBar.SelectedIndex = index;
}
else if (e.PropertyName == TabbedPage.BarBackgroundColorProperty.PropertyName)
{
UpdateBarBackgroundColor();
}
else if (e.PropertyName == TabbedPage.BarTextColorProperty.PropertyName)
{
UpdateBarTextColor();
}
else if (e.PropertyName == TopTabbedPage.BarIndicatorColorProperty.PropertyName)
{
UpdateBarIndicatorColor();
}
else if (e.PropertyName == TopTabbedPage.SwipeEnabledColorProperty.PropertyName)
{
UpdateSwipe(Tabbed.SwipeEnabled);
}
}
public override UIViewController ChildViewControllerForStatusBarHidden()
{
var current = Tabbed.CurrentPage;
if (current == null)
return null;
return GetViewController(current);
}
void UpdateSwipe(bool swipeEnabled)
{
pageViewController.WeakDataSource = swipeEnabled ? this : null;
}
void Reset()
{
var i = 0;
foreach (var page in Tabbed.Children)
{
SetupPage(page, i++);
}
}
void SetControllers()
{
var list = new List<UIViewController>();
var titles = new List<string>();
for (var i = 0; i < Tabbed.Children.Count; i++)
{
var child = Tabbed.Children[i];
var v = child as VisualElement;
if (v == null)
continue;
var renderer = Platform.GetRenderer(v);
if (renderer == null) continue;
list.Add(renderer.ViewController);
titles.Add(Tabbed.Children[i].Title);
}
ViewControllers = list.ToArray();
TabBar.SetItems(titles);
}
void SetupPage(Page page, int index)
{
IVisualElementRenderer renderer = Platform.GetRenderer(page);
if (renderer == null)
{
renderer = Platform.CreateRenderer(page);
Platform.SetRenderer(page, renderer);
}
page.PropertyChanged -= OnPagePropertyChanged;
page.PropertyChanged += OnPagePropertyChanged;
}
void TeardownPage(Page page, int index)
{
page.PropertyChanged -= OnPagePropertyChanged;
Platform.SetRenderer(page, null);
}
void UpdateBarBackgroundColor()
{
if (Tabbed == null || TabBar == null)
return;
var barBackgroundColor = Tabbed.BarBackgroundColor;
if (!_defaultBarColorSet)
{
_defaultBarColor = TabBar.BackgroundColor;
_defaultBarColorSet = true;
}
TabBar.BackgroundColor = barBackgroundColor.ToUIColor();
}
void UpdateBarTextColor()
{
TabBar.TextColor = Tabbed.BarTextColor.ToUIColor();
}
void UpdateBarIndicatorColor()
{
TabBar.IndicatorColor = Tabbed.BarIndicatorColor.ToUIColor();
}
}
}
This is my code
gridView1.Columns.Add(new DevExpress.XtraGrid.Columns.GridColumn()
{
Caption = "Selected",
ColumnEdit = new RepositoryItemCheckEdit() { },
VisibleIndex = 1,
UnboundType = DevExpress.Data.UnboundColumnType.Boolean
});
But I cant check multiple checkEdit at the same time.
Why was that?
And please show me the way out.
Thanks.
Well, there are two answers to that question, one very simple, and one very complex, let's start with the simple:
If you want to have an column that has the "Selected" caption and act as a checkbox to indicate that a particular record was selected, you have two options:
1) If you can alter the class in your data source to add a property that is bool and could be used with DataBinding, then, all is done in a very simple way, jast add the property and bind the data and it will work:
class SimplePerson
{
public string Name { get; set; }
public bool IsSelected { get; set; }
}
BindingList<SimplePerson> source = new BindingList<SimplePerson>();
void InitGrid()
{
source.Add(new SimplePerson() { Name = "John", IsSelected = false });
source.Add(new SimplePerson() { Name = "Gabriel", IsSelected = true });
gridControl.DataSource = source;
}
2) You cannot alter you classes, so you need to this by signing the correct grid events and drawing the column yourself, and also adding the right handlers for all the actions.... is a very complex case, but for your luck i have this done, because i have had this problem in the past, so i will post you my full class!
public class GridCheckMarksSelection
{
public event EventHandler SelectionChanged;
protected GridView _view;
protected ArrayList _selection;
private GridColumn _column;
private RepositoryItemCheckEdit _edit;
public GridView View
{
get { return _view; }
set
{
if (_view == value)
return;
if (_view != null)
Detach();
_view = value;
Attach();
}
}
public GridColumn CheckMarkColumn { get { return _column; } }
public int SelectedCount { get { return _selection.Count; } }
public GridCheckMarksSelection()
{
_selection = new ArrayList();
}
public GridCheckMarksSelection(GridView view)
: this()
{
this.View = view;
}
protected virtual void Attach()
{
if (View == null)
return;
_selection.Clear();
_view = View;
_edit = View.GridControl.RepositoryItems.Add("CheckEdit")
as RepositoryItemCheckEdit;
_edit.EditValueChanged += edit_EditValueChanged;
_column = View.Columns.Insert(0);
_column.OptionsColumn.AllowSort = DefaultBoolean.False;
_column.VisibleIndex = int.MinValue;
_column.FieldName = "CheckMarkSelection";
_column.Caption = "Mark";
_column.OptionsColumn.ShowCaption = false;
_column.UnboundType = UnboundColumnType.Boolean;
_column.ColumnEdit = _edit;
View.CustomDrawColumnHeader += View_CustomDrawColumnHeader;
View.CustomDrawGroupRow += View_CustomDrawGroupRow;
View.CustomUnboundColumnData += view_CustomUnboundColumnData;
View.MouseUp += view_MouseUp;
}
protected virtual void Detach()
{
if (_view == null)
return;
if (_column != null)
_column.Dispose();
if (_edit != null)
{
_view.GridControl.RepositoryItems.Remove(_edit);
_edit.Dispose();
}
_view.CustomDrawColumnHeader -= View_CustomDrawColumnHeader;
_view.CustomDrawGroupRow -= View_CustomDrawGroupRow;
_view.CustomUnboundColumnData -= view_CustomUnboundColumnData;
_view.MouseDown -= view_MouseUp;
_view = null;
}
protected virtual void OnSelectionChanged(EventArgs e)
{
if (SelectionChanged != null)
SelectionChanged(this, e);
}
protected void DrawCheckBox(Graphics g, Rectangle r, bool Checked)
{
var info = _edit.CreateViewInfo() as CheckEditViewInfo;
var painter = _edit.CreatePainter() as CheckEditPainter;
ControlGraphicsInfoArgs args;
info.EditValue = Checked;
info.Bounds = r;
info.CalcViewInfo(g);
args = new ControlGraphicsInfoArgs(info, new GraphicsCache(g), r);
painter.Draw(args);
args.Cache.Dispose();
}
private void view_MouseUp(object sender, MouseEventArgs e)
{
if (e.Clicks == 1 && e.Button == MouseButtons.Left)
{
GridHitInfo info;
var pt = _view.GridControl.PointToClient(Control.MousePosition);
info = _view.CalcHitInfo(pt);
if (info.InRow && _view.IsDataRow(info.RowHandle))
UpdateSelection();
if (info.InColumn && info.Column == _column)
{
if (SelectedCount == _view.DataRowCount)
ClearSelection();
else
SelectAll();
}
if (info.InRow && _view.IsGroupRow(info.RowHandle)
&& info.HitTest != GridHitTest.RowGroupButton)
{
bool selected = IsGroupRowSelected(info.RowHandle);
SelectGroup(info.RowHandle, !selected);
}
}
}
private void View_CustomDrawColumnHeader
(object sender, ColumnHeaderCustomDrawEventArgs e)
{
if (e.Column != _column)
return;
e.Info.InnerElements.Clear();
e.Painter.DrawObject(e.Info);
DrawCheckBox(e.Graphics, e.Bounds, SelectedCount == _view.DataRowCount);
e.Handled = true;
}
private void View_CustomDrawGroupRow
(object sender, RowObjectCustomDrawEventArgs e)
{
var info = e.Info as GridGroupRowInfo;
info.GroupText = " " + info.GroupText.TrimStart();
e.Info.Paint.FillRectangle
(e.Graphics, e.Appearance.GetBackBrush(e.Cache), e.Bounds);
e.Painter.DrawObject(e.Info);
var r = info.ButtonBounds;
r.Offset(r.Width * 2, 0);
DrawCheckBox(e.Graphics, r, IsGroupRowSelected(e.RowHandle));
e.Handled = true;
}
private void view_CustomUnboundColumnData
(object sender, CustomColumnDataEventArgs e)
{
if (e.Column != CheckMarkColumn)
return;
if (e.IsGetData)
e.Value = IsRowSelected(View.GetRowHandle(e.ListSourceRowIndex));
else
SelectRow(View.GetRowHandle(e.ListSourceRowIndex), (bool)e.Value);
}
private void edit_EditValueChanged(object sender, EventArgs e)
{
_view.PostEditor();
}
private void SelectRow(int rowHandle, bool select, bool invalidate)
{
if (IsRowSelected(rowHandle) == select)
return;
object row = _view.GetRow(rowHandle);
if (select)
_selection.Add(row);
else
_selection.Remove(row);
if (invalidate)
Invalidate();
OnSelectionChanged(EventArgs.Empty);
}
public object GetSelectedRow(int index)
{
return _selection[index];
}
public int GetSelectedIndex(object row)
{
return _selection.IndexOf(row);
}
public void ClearSelection()
{
_selection.Clear();
View.ClearSelection();
Invalidate();
OnSelectionChanged(EventArgs.Empty);
}
private void Invalidate()
{
_view.CloseEditor();
_view.BeginUpdate();
_view.EndUpdate();
}
public void SelectAll()
{
_selection.Clear();
var dataSource = _view.DataSource as ICollection;
if (dataSource != null && dataSource.Count == _view.DataRowCount)
_selection.AddRange(dataSource); // fast
else
for (int i = 0; i < _view.DataRowCount; i++) // slow
_selection.Add(_view.GetRow(i));
Invalidate();
OnSelectionChanged(EventArgs.Empty);
}
public void SelectGroup(int rowHandle, bool select)
{
if (IsGroupRowSelected(rowHandle) && select) return;
for (int i = 0; i < _view.GetChildRowCount(rowHandle); i++)
{
int childRowHandle = _view.GetChildRowHandle(rowHandle, i);
if (_view.IsGroupRow(childRowHandle))
SelectGroup(childRowHandle, select);
else
SelectRow(childRowHandle, select, false);
}
Invalidate();
}
public void SelectRow(int rowHandle, bool select)
{
SelectRow(rowHandle, select, true);
}
public bool IsGroupRowSelected(int rowHandle)
{
for (int i = 0; i < _view.GetChildRowCount(rowHandle); i++)
{
int row = _view.GetChildRowHandle(rowHandle, i);
if (_view.IsGroupRow(row))
if (!IsGroupRowSelected(row))
return false;
else
if (!IsRowSelected(row))
return false;
}
return true;
}
public bool IsRowSelected(int rowHandle)
{
if (_view.IsGroupRow(rowHandle))
return IsGroupRowSelected(rowHandle);
object row = _view.GetRow(rowHandle);
return GetSelectedIndex(row) != -1;
}
public void UpdateSelection()
{
_selection.Clear();
Array.ForEach(View.GetSelectedRows(), item => SelectRow(item, true));
}
}
And now you need to know how to use this:
void InitGrid()
{
gridControl.DataSource = source;
// Do this after the database for the grid is set!
selectionHelper = new GridCheckMarksSelection(gridView1);
// Define where you want the column (0 = first)
selectionHelper.CheckMarkColumn.VisibleIndex = 0;
// You can even subscrive to the event that indicates that
// there was change in the selection.
selectionHelper.SelectionChanged += selectionHelper_SelectionChanged;
}
void selectionHelper_SelectionChanged(object sender, EventArgs e)
{
// Do something when the user selects or unselects something
}
But how do you retrieve all the selected items? There is a example assuming that the type bond is 'Person'
/// <summary>
/// Return all selected persons from the Grid
/// </summary>
public IList<Person> GetItems()
{
var ret = new List<Person>();
Array.ForEach
(
gridView1.GetSelectedRows(),
cell => ret.Add(gridView1.GetRow(cell) as Person)
);
return ret;
}