I have a custom ViewCell. I was wondering if I could add a command to it, which would navigate to another page, when the ViewCell is clicked.
Below is what I have so far:
public class MainMenuItem : ViewCell
{
public MainMenuItem(string text, string icon)
{
View = new StackLayout()
{
Spacing = 10,
Padding = 10,
Orientation = StackOrientation.Horizontal,
VerticalOptions = LayoutOptions.Center,
Children = {
new Image() {
Source = ImageSource.FromFile(icon),
HorizontalOptions = LayoutOptions.Start,
HeightRequest = 30,
WidthRequest = 30,
},
new Label() {
Text = text,
HorizontalOptions = LayoutOptions.Start,
FontSize = 18,
},
new Image() {
Source = ImageSource.FromFile("listitem_next.png"),
HeightRequest = 12,
HorizontalOptions = LayoutOptions.EndAndExpand
}
}
};
View = View;
}
}
Above is my view cell. Now I render these within Table sections of a TableView. And here is the code for that:
TableView tvProfile = new TableView
{
HasUnevenRows = true,
Intent = TableIntent.Form,
Root = new TableRoot {,
new TableSection ("Emergency Contacts")
{
new MainMenuItem("Contacts", "icon_phone.png")
},
new TableSection ("Check in Timers")
{
new MainMenuItem("Timers", "icon_clock.png")
},
new TableSection ("Medical Information")
{
new MainMenuItem("Medcial Info", "icon_medicalkit.png")
}
}
};
What I want is, when the user selects an Item (the ViewCell), I want to navigate the user to the respective page.
How do I do this using a command? If it's even possible. I'm new to using commands, so whatever I've gotten on the web has gone over my head.
Any help on this would be HUGELY appreciated.
Here is a quick'n'dirty implementation. Add command and commandParameter to constructor and add GestureRecognizer which calls this command.
public class MainMenuItem : ViewCell
{
public MainMenuItem(string text, string icon, ICommand command, Func<Page> commandParameterFunc)
{
View = new StackLayout()
{
...
};
View.GestureRecognizers.Add(new TapGestureRecognizer { Command = command, CommandParameter = commandParameterFunc });
}
}
Then do the following changes - create a command and add 2 parameters to every cell. Here you define the command, what happens when you click and a parameter for the command, which is a Page (but you should not create it here already and check for errors).
Update: I changed to pass a function instead of an object, so the Page is created on click. Still a bit dirty ;)
public class MyPage : ContentPage
{
private readonly ICommand _navigateCommand;
public MyPage()
{
_navigateCommand = new Command(commandParamter => Navigation.PushModalAsync((commandParamter as Func<Page>)())));
TableView tvProfile = new TableView
{
HasUnevenRows = true,
Intent = TableIntent.Form,
Root = new TableRoot
{
new TableSection ("Emergency Contacts")
{
new MainMenuItem("Contacts", "icon_phone.png", _navigateCommand, () => new ContactsPage())
},
new TableSection ("Check in Timers")
{
new MainMenuItem("Timers", "icon_clock.png", _navigateCommand, () => new TimersPage())
},
new TableSection ("Medical Information")
{
new MainMenuItem("Medcial Info", "icon_medicalkit.png", _navigateCommand, () => new MedicalInfoPage())
}
}
};
Content = tvProfile;
}
}
Related
I have a template frame that provides some padding and accepts multiple elements:
[Xamarin.Forms.ContentProperty("Contents")]
public class ContentFrame : StackLayout
{
public StackLayout ContentStack = new StackLayout();
public IList<View> Contents { get => ContentStack.Children; }
public ContentFrame()
{
CustomFrame cf = new CustomFrame()
{
Content = ContentStack,
HasShadow = false,
};
cf.SetDynamicResource(BackgroundColorProperty, "ContentFrameBackgroundColor");
cf.SetDynamicResource(Frame.CornerRadiusProperty, "ContentFrameCornerRadius");
cf.SetDynamicResource(MarginProperty, "ContentFrameMargin");
this.Children.Add(cf);
}
I would like to add child labels like this: c1.Children.Add - But when I do this the BackgroundColor, CornerRadius and Margin don't get used (see first part of image for ABC and ABC)
The only way I can get it to use these is by exposing ContentStack as a public property and by adding to that (see below for ABC and GHI)
public class TestPage : HeadingView
{
public TestPage() : base()
{
var s = new Stack();
var c1 = new ContentFrame();
c1.Children.Add(new Label() { Text = "ABC" });
c1.Children.Add(new Label() { Text = "DEF" });
var c2 = new ContentFrame();
c2.ContentStack.Children.Add(new Label() { Text = "DEF" });
c2.ContentStack.Children.Add(new Label() { Text = "GHI" });
s.Children.Add(c1);
s.Children.Add(c2);
this.InnerContent = s;
}
}
Question > Can anyone explain why the first case (with get => ContentStack.Children) doesn't show the frame background, radius etc.
make ContentStack private and add a method to expose the add functionality
private StackLayout ContentStack = new StackLayout();
public void Add(View view)
{
this.ContentStack.Children.Add(view);
}
if you want to add multiple you could also do
public void Add(List<View> views)
{
foreach(var v in views)
{
this.ContentStack.Children.Add(v);
}
}
I'm trying to bind a button in a DataTemplate that is being used by a collection view.
This is my button inside the DataTemplate, and I want to bind it to a command in "DetailDayPageViewModel".
Button viewComment = new Button()
{
TextColor = Color.DodgerBlue,
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Start,
FontSize = 16
};
// this binding does not work
viewComment.SetBinding(Button.CommandProperty, nameof(DetailDayPageViewModel.ViewComment));
Use RelativeBinding for binding values of Page's BindingContext to property inside DataTemplate.
There are two ways to do this:
1: Binding through the ViewModel. ReltiveBinding of Mode FindAncestorBindingContext
public class ItemView : Grid
{
public ItemView()
{
Button clickButton = new Button() { Text = "Hi there" };
clickButton.SetBinding(
Button.CommandProperty,
new Binding(
"ItemClickCommand",
source: new RelativeBindingSource(
RelativeBindingSourceMode.FindAncestorBindingContext,
typeof(ViewModel))
));
this.Children.Add(clickButton);
}
}
2: Binding through the Parent view BindingContext:
public class ItemView : Grid
{
public ItemView()
{
Button clickButton = new Button() { Text = "Hi there" };
clickButton.SetBinding(
Button.CommandProperty,
new Binding(
"BindingContext.ItemClickCommand",
source: new RelativeBindingSource(
RelativeBindingSourceMode.FindAncestor,
typeof(CollectionView))
));
this.Children.Add(clickButton);
}
}
Please do check and see if it helps!! Comment for any queries.
I think you can take a look to this sample:
TestBinding
It refers to a ListView, but it should be applicated to a CollectionView.
You need to set the the "source":
TapGestureRecognizer tgrUpDown2 = new TapGestureRecognizer();
tgrUpDown2.SetBinding(TapGestureRecognizer.CommandProperty, new Binding("BindingContext.UpDown2Command", source: this));
tgrUpDown2.SetBinding(TapGestureRecognizer.CommandParameterProperty, ".");
then in your Model, you have the "parameter" passend...
this.UpDown2Command = new Command(async (object obj) =>
{
try
{
if (_isTapped)
return;
if (obj != null)
System.Diagnostics.Debug.WriteLine("Obj is not null");
else
System.Diagnostics.Debug.WriteLine("Obj IS null");
_isTapped = true;
int idx = List.IndexOf((Model)obj);
List[idx].Checked1 = !List[idx].Checked1;
_isTapped = false;
}
catch (Exception ex)
{
_isTapped = false;
await Application.Current.MainPage.DisplayAlert("Attention", ex.Message, "Ok");
}
});
This is a useful link I have found some years ago:
listview-in-xamarin-forms-in-mvvm
If you want a "class" to define your ViewCell, you can assign the class in this way:
lv.ItemTemplate = new DataTemplate(() =>
{
return new MyViewCell(lv);
});
Where MyViewCell is something like:
class MyViewCell : ViewCell
{
public MyViewCell(ListView lv)
{
StackLayout slView = new StackLayout();
slView.SetBinding(StackLayout.BackgroundColorProperty, "BackgroundColor");
Label lDesc = new Label();
lDesc.SetBinding(Label.TextProperty, "Description", stringFormat: "DESCRIPTION: {0}");
lDesc.SetBinding(Label.TextColorProperty, "TextColor");
// LABEL QTY
TapGestureRecognizer tgrQty = new TapGestureRecognizer();
tgrQty.SetBinding(TapGestureRecognizer.CommandProperty, new Binding("BindingContext.QtyCommand", source: lv));
tgrQty.SetBinding(TapGestureRecognizer.CommandParameterProperty, ".");
....
....
View = slView;
}
}
You can pass "ListView" in the constructor so you can use it in "source" binding property.
After searching multiple blogs and videos I find that to implement the UITableView one can use MvxTableViewController, but what to use for NSTableView?
I do not find any tutorial, example that covers OSX binding TableView using MvvmCross. Any leads will be appreciated.
We don't have MvxTableViewController for macOS.
However, if you abstract from that, binding to a NSTableView is very similar to a UITableView on iOS.
private NSTableView _tableView;
public override void ViewDidLoad()
{
base.ViewDidLoad();
_tableView = new NSTableView();
// add constraints or size otherwise
var source = new MvxTableViewSource(_tableView);
_tableView.Source = source;
var set = this.CreateBindingSet<MyViewController, MyViewModel>();
set.Bind(source).For(v => v.ItemsSource).To(vm => vm.Items);
set.Apply();
}
This will bind the ViewModel Items to the ItemsSource. However, you will still need to specify what to bind in the cell. The simplest way to do this is to provide a TableColumn.
var column = new MvxTableColumn();
column.Identifier = "First";
column.BindingText = "Text Name";
column.HeaderCell = new NSCell("Example");
_tableView.AddColumn(column);
This will bind the Text property of the TableColumn to Name in the items provided in Items in the ViewModel.
If you need more than this you will need to subclass MvxTableViewSource and override GetOrCreateViewFor and in there provide your own subclass of MvxTableCellView where you do more. This could look something as follows.
public class MyCustomCell : MvxTableCellView
{
public MyCustomCell(IntPtr handle) : base(handle)
{
}
public MyCustomCell(string bindingText) : base(bindingText)
{
this.Frame = new CGRect(0, 0, 100, 50);
TextField = new NSTextField(new CGRect(50, 0, 100, 50))
{
Editable = false,
Bordered = false
};
ImageView = new NSImageView(new CGRect(0, 0, 50, 50));
AddSubview(TextField);
AddSubview(ImageView);
this.Initialize(bindingText);
}
private string _imageUrl;
public string ImageUrl
{
get => _imageUrl;
set
{
_imageUrl = value;
ImageService.Instance.LoadUrl(_imageUrl).Into(ImageView);
}
}
}
And the table source:
public class MyTableSource : MvxTableViewSource
{
private string _bindingText;
public MyTableSource(NSTableView tableView, string bindingText) : base(tableView)
{
_bindingText = bindingText;
}
public override NSView GetViewForItem(NSTableView tableView, NSTableColumn tableColumn, nint row)
{
if (ItemsSource == null)
return null;
var item = ItemsSource.ElementAt((int)row);
var view = new MyCustomCell(_bindingText);
if (view is IMvxDataConsumer bindable)
bindable.DataContext = item;
return view;
}
}
Then instead of using MvxTableViewSource in the first example, use your own MyTableSource instead:
var source = new MyTableViewSource(_tableView, "Text Name; ImageUrl Url");
_tableView.Source = source;
Where Name and Url are in the Item in the Items bound to the ItemsSource.
public DataTemplate CreateQuestionAnswerRadioButtonTemplate(string question, List<string> answers){
DataTemplate template = new DataTemplate(() =>
{
StackLayout parentLayout = new StackLayout()
{
Padding = new Thickness(20, 20, 20, 20),
HeightRequest = 500,
};
ScrollView surveyScrollView = new ScrollView()
{
Orientation = ScrollOrientation.Vertical,
};
StackLayout questionLayout = new StackLayout()
{
Padding = new Thickness(5, 5, 5, 5),
HeightRequest = 500,
};
Label questLabel = new Label();
questLabel.Text = question;
questLabel.TextColor = Color.FromHex("#EF4D80");
questLabel.FontAttributes = FontAttributes.Bold;
questLabel.FontSize = 18;
BindableRadioGroup radioGroup = new BindableRadioGroup(false);
radioGroup.ItemsSource = answers;
questionLayout.Children.Add(questLabel);
questionLayout.Children.Add(radioGroup);
surveyScrollView.Content = questionLayout;
parentLayout.Children.Add(surveyScrollView);
return parentLayout;
});
return template;
}
Adding these Data Templates to a List.
new CarouselView
{
Margin = new Thickness(0, 20, 0, 0),
ItemsSource = dataTemplates,
ItemTemplate = dataTemplates[0],
};
Now when I swipe the Carousel, How do I load dataTemplates[1 or 2 or 3] ??
I have a Next Button in which in am setting the item source of the Carousel View to dataTemplates[1] but the template does not get updated
Pls Suggest the right approach ?
dataTemplates = new List<DataTemplate>();
dataTemplates.Add(CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_1, SurveyQuestion_1_Answers));
dataTemplates.Add(CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_3, SurveyQuestion_3_Answers));
dataTemplates.Add(CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_4, SurveyQuestion_4_Answers));
dataTemplates.Add(CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_5, SurveyQuestion_5_Answers));
You need a DataTemplateSelector for your CarouselView.
in your code behind of the page:
new CarouselView
{
Margin = new Thickness(0, 20, 0, 0),
ItemsSource = dataTemplates,
ItemTemplate = new SurveyDataTemplateSelector()
};
SurveyDataTemplateSelector
DataTemplate survey1Template;
DataTemplate survey3Template;
DataTemplate survey4Template;
DataTemplate survey5Template;
public SurveyDataTemplateSelector()
{
survey1Template = CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_1, SurveyQuestion_1_Answers);
survey3Template = CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_3, SurveyQuestion_3_Answers);
survey4Template = CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_4, SurveyQuestion_4_Answers);
survey5Template = CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_5, SurveyQuestion_5_Answers);
}
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
// Here you define which DataTemplate is selected, for example:
if (item == null)
{
return null;
}
SurveyAnswers answers = item as SurveyAnswers;
if (answers.question == 1)
{
return survey1Template;
}
else if (answers.question == 3)
{
return survey3Template;
}
else if (answers.question == 4)
{
return survey4Template;
}
else if (answers.question == 5)
{
return survey5Template;
}
return null;
}
Check this link for a good documentation by Xamarin: https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/templates/data-templates/selector/
I have used Segmented Control in my application. I don't know how to add two content pages to Segment control like a tabbed page. I have attached the sample file. Please give any suggestion Link for Sample Application
Sample Code:
public partial class SamplePage : ContentPage
{
SegmentedControl segControl;
SegmentedControlOption optionOne;
SegmentedControlOption optionTwo;
public SamplePage()
{
segControl = new SegmentedControl();
optionOne = new SegmentedControlOption();
optionTwo = new SegmentedControlOption();
optionOne.Text = "One";
optionTwo.Text = "Two";
segControl.Children.Add(optionOne);
segControl.Children.Add(optionTwo);
var stack = new StackLayout()
{
VerticalOptions = LayoutOptions.StartAndExpand,
HorizontalOptions = LayoutOptions.CenterAndExpand,
Children = { segControl }
};
this.Content = stack;
}
}
ScreenShot Attached
Just some suggestions and explanations.
We can't put a ContentPage inside another ContentPage
It's better to use ContentView instead of ContentPage
Grid is more recommended in this scenario , since it fills with the whole Screen.
Use ValueChanged event to change the view dynamically.
Code :
Page
public partial class SegmentedAppPage : ContentPage
{
SegmentedControl segControl;
SegmentedControlOption scOptionOne;
SegmentedControlOption scOptionTwo;
Grid grid;
View1 view1 = new View1();
View2 view2 = new View2();
public SegmentedAppPage()
{
InitializeComponent();
segControl = new SegmentedControl();
segControl.SelectedValue = "One";
scOptionOne = new SegmentedControlOption();
scOptionTwo = new SegmentedControlOption();
scOptionOne.Text = "One";
scOptionTwo.Text = "Two";
segControl.Children.Add(scOptionOne);
segControl.Children.Add(scOptionTwo);
grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.Children.Add(segControl, 0, 0);
grid.Children.Add(view1, 0, 1);
this.Content = grid;
segControl.ValueChanged += SegControl_ValueChanged;
}
private void SegControl_ValueChanged(object sender, EventArgs e)
{
SegmentedControl control = sender as SegmentedControl;
if(control.SelectedValue is "One")
{
grid.Children.Remove(view2);
grid.Children.Add(view1,0,1); //This line
}
else if (control.SelectedValue is "Two")
{
grid.Children.Remove(view1);
grid.Children.Add(view2, 0, 1); //This line
}
this.Content = grid;
}
}
ContentView
public class View1 : ContentView
{
public View1()
{
Content = new StackLayout
{
BackgroundColor = Color.Green,
Children = {
new Label { Text = "View1" }
}
};
}
}
To set default value on segmentedControl , modify code in SegmentedControlRenderers
protected override void OnElementChanged(ElementChangedEventArgs<SegmentedControl> e)
{
base.OnElementChanged(e);
var segmentedControl = new UISegmentedControl();
for (var i = 0; i < e.NewElement.Children.Count; i++)
{
segmentedControl.InsertSegment(e.NewElement.Children[i].Text, i, false);
}
segmentedControl.ValueChanged += (sender, eventArgs) => {
e.NewElement.SelectedValue = segmentedControl.TitleAt(segmentedControl.SelectedSegment);
};
segmentedControl.SelectedSegment = 0; // add this line
SetNativeControl(segmentedControl);
}
Test