I need to create a line which is a made up of multiple BoxViews (WidthRequest=0.20830 and HeightRequest=5 for every BoxView). There will be 1440 BoxViews in a sequence arranged in way that it will create a line(approximately WidthRequest=300).
My Code -
public partial class timeManagement : ContentPage
{
double oneMinute=0.20833333;
public timeManagement ()
{
InitializeComponent ();
StackLayout stack = new StackLayout{Orientation=StackOrientation.Horizontal,
};
for(int i=1;i<=14;i++)
{
BoxView piece_ofLine = new BoxView
{
HeightRequest=5,
WidthRequest=5,
Color=Color.Red
};
if (i >= 5 && i <= 9) {
stack.Children.Add (piece_ofLine);
piece_ofLine.Color = Color.Green;
} else {
stack.Children.Add (piece_ofLine);
piece_ofLine.Color = Color.Red;
}
}
Content = new StackLayout {
Padding =50,
Spacing=0,
Children = {
stack
}
};
}
}
And output is-
But I want all boxes side by side so it will appear like a single line.
There are a few issues here
The default orientation for a StackLayout is Vertical so you would need to set that on the stack variable
You would need to create a new instance for each BoxView that you add to the stack. Otherwise it will just keep adding the same one over and over. And in the end you would just have one.
I assume that you would want them directly to the side of each other. If this is the case, I think that it would be safest to explicitly set the Spacing of your StackLayout to 0
Related
I've been advised that Xamarin doesn't support nested ListViews.. I trying to build social app, in which the post might have several images and comments.
The feed page will be basically a ListView of the posts. While I cannot use a listView within the post (which is a viewCell), I tried to use Grid for the images instead, and another Grid for Comments. Both Images and Comments can be Lists, Arrays or ObservableCollections in the "post" Class.
Now I need to make foreach loop of this array/list, adding images with source bound to array item. But it seems to me, I couldn't use the data passed to the viewCell in C# (can only be used in Xaml layout).
Anybody has any ideas how to solve this.
[DataContract]
Public class post {
[DataMember]
List<comment> commentContainer {get; set;}
[DataMember]
List<String> imageContainer {get; set;}
}
in Xaml of cellView
<Grid x:Name="imagesGrid" IsVisible="{Binding isImage}" BindingContext="{Binding imageContainer}">
here I need to iterate the list of image in c# if possible - runtime- !
</Grid>
You are right to say that you shouldn't use nested ListViews in Xamarin.Forms, virtualization goes out the window and scrolling/gestures will be hard to handle.
However, i think this design in Xamarin.Forms will be fraught with problems. Xamarin.Forms ListView is a very specific beast and wont play well with very complicated layouts. My gut feeling is you want to do this in Native Views maybe or at the very least design this entirely in code. Anyway that is just my gut feel.
Have it be noted, i would shy away from this design and make it simple for your self by doing like WhatsApp does, and use a Template Selector and break apart multiple images and posts with a DataTemplateSelecto. Then you Don't have to worry about complicated layouts.
However, if you have your heart set on more complicated layouts inside ListView (be warned there be dragons) though you can take a look at this, Cell Appearance As noted i would be more inclined to this entirely in Code apposed to an ad-hock Xaml solution
public class CustomCell : ViewCell
{
public CustomCell()
{
//instantiate each of our views
var image = new Image ();
StackLayout cellWrapper = new StackLayout ();
StackLayout horizontalLayout = new StackLayout ();
Label left = new Label ();
Label right = new Label ();
...
//add views to the view hierarchy
horizontalLayout.Children.Add (image);
horizontalLayout.Children.Add (left);
horizontalLayout.Children.Add (right);
cellWrapper.Children.Add (horizontalLayout);
View = cellWrapper;
}
}
And set it like this
public partial class ImageCellPage : ContentPage
{
public ImageCellPage ()
{
InitializeComponent ();
listView.ItemTemplate = new DataTemplate (typeof(CustomCell));
}
}
Also checkout Grid
You can build one in code like this
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star)});
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star)});
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star)});
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star)});
var topLeft = new Label { Text = "Top Left" };
var topRight = new Label { Text = "Top Right" };
var bottomLeft = new Label { Text = "Bottom Left" };
var bottomRight = new Label { Text = "Bottom Right" };
grid.Children.Add(topLeft, 0, 0);
grid.Children.Add(topRight, 0, 1);
grid.Children.Add(bottomLeft, 1, 0);
grid.Children.Add(bottomRight, 1, 1);
I've got a stack layout inside a gridview, that I use as a sort of list.
The "items" in the list have to be click/tap -able, but I can't find a way to make the child area clickable while also giving a value to the event to know what item the user clicked.
Right now I create 20 items in a for loop. The "i" is an integer used for counting.
I put an BoxView over the original boxview and label of the item, which is transparent and gets the gesturerecognizer.
var clickableBoxv = new BoxView
{
BackgroundColor = Color.Transparent,
Margin = new Thickness(0, 5, 0, 5)
};
clickableBoxv.GestureRecognizers.Add(new TapGestureRecognizer
{
Command = new Command(() => Item_Clicked(i)),
});
private void Item_Clicked(int num)
{
DisplayAlert("Alert", num.ToString(), "OK");
}
But when I click the item, it shows an alert with only the last added number. (which didn't surprise me). But how can I get the alert to show me the specific item number?
While I haven't tested your exact code I have reproduced the same behaviour with some Actions inside a for loop.
Option 1 - Track the boxes.
It might not be the most ideal of solutions but one option would be to keep track of your boxes and use their index in the collection to represent the number.
// 1. A place to store the boxes.
IList<BoxView> boxes = new List<BoxView>();
var clickableBoxv = new BoxView
{
BackgroundColor = Color.Transparent,
Margin = new Thickness(0, 5, 0, 5)
};
// 2. Keep track of your clickable boxes.
boxes.Add(clickableBoxv);
clickableBoxv.GestureRecognizers.Add(new TapGestureRecognizer
{
// 3. Pass in the box rather than the int.
Command = new Command(() => Item_Clicked(clickableBoxv)),
});
private void Item_Clicked(BoxView box)
{
// 4. Use the index as the number.
DisplayAlert("Alert", boxes.IndexOf(box).ToString(), "OK");
}
Option 2 - sub class BoxView
// 1. Sub class
public class MyBoxView : BoxView
{
public int Index { get; set; }
}
// 2. Use new sub class
var clickableBoxv = new MyBoxView
{
BackgroundColor = Color.Transparent,
Margin = new Thickness(0, 5, 0, 5),
Index = i,
};
clickableBoxv.GestureRecognizers.Add(new TapGestureRecognizer
{
// 3. Pass in the box rather than the int.
Command = new Command(() => Item_Clicked(clickableBoxv)),
});
private void Item_Clicked(MyBoxView box)
{
// 4. Use the index as the number.
DisplayAlert("Alert", box.Index.ToString(), "OK");
}
I have a problem with a ListView. I want each Cell to have a label and a switch but the text of the label does not appear.
Here is my code:
public class FilterPage : ContentPage
{
public FilterPage()
{
List<FilterCell> listContent = new List<FilterCell>();
foreach(string type in Database.RestaurantTypes)
{
FilterCell fc = new FilterCell();
fc.Text = type;
listContent.Add(fc);
}
ListView types = new ListView();
types.ItemTemplate = new DataTemplate(typeof(FilterCell));
types.ItemsSource = listContent;
var layout = new StackLayout();
layout.Children.Add(types);
Content = layout;
}
}
public class FilterCell : ViewCell
{
private Label label;
public Switch CellSwitch { get; private set; }
public String Text{ get { return label.Text; } set { label.Text = value; } }
public FilterCell()
{
label = new Label();
CellSwitch = new Switch();
var layout = new StackLayout
{
Padding = new Thickness(20, 0, 0, 0),
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.FillAndExpand,
Children = { label, CellSwitch }
};
View = layout;
}
}
If I enter a fixed Text in the FilterCell-Constructor it works fine (e.g.: label.Text = "Hello World")
When I create a Method for the ItemSelected-Event and read out the SelectedItem.Text Property I get the text I assigned as Value but it's never displayed. Only the switch is displayed when I try to run this Code.
Thanks for your help
Niko
Ohh boy. This code looks like a rape (sorry I had to say this).
Now let's see what's wrong:
The reason is you are mixing up data and view heavily.
The line
types.ItemTemplate = new DataTemplate(typeof(FilterCell));
means: "For each item in the list (ItemsSource) create a new filter cell". The FilterCells that you create in the loop are never displayed.
The easy fix
public class FilterPage : ContentPage
{
public FilterPage()
{
var restaurantTypes = new[] {"Pizza", "China", "German"}; // Database.RestaurantTypes
ListView types = new ListView();
types.ItemTemplate = new DataTemplate(() =>
{
var cell = new SwitchCell();
cell.SetBinding(SwitchCell.TextProperty, ".");
return cell;
});
types.ItemsSource = restaurantTypes;
Content = types;
}
}
There is a standard cell type that contains a label and a switch SwitchCell, use it.
As ItemsSource of your list, you have to use your data. In your case the list of restaurant types. I just mocked them with a static list.
The DataTemplate creates the SwitchCell and sets the Databinding for the Text property. This is the magic glue between View and data. The "." binds it to the data item itself. We use it, because our list contains items of strings and the Text should be exactly the string. (read about Databinding: https://developer.xamarin.com/guides/xamarin-forms/getting-started/introduction-to-xamarin-forms/#Data_Binding )
I striped away the StackLayout that contained the list. You can directly set the list as Content of the page.
Lesson
use standard controls, if possible
You should always try to remember to keep data and view apart from each other and use data binding to connect to each other.
Try to avoid unnecessary views.
Basically I want to make functionality, when I input text to my Editor it will appear inserted data to my label. And if I will swipe page to another page, that data should be bind'ed to that label in previous page where I entered data.
So I have portable class. In that class I have method public ContentPage CreatePage(MyObject thing) here I define many Labels, boxes , buttons and etc. But I will indicate most important things: Here I am define my Label and Editor:
public partial class CoolPage: CarouselPage
{
public CoolPage()
{
foreach (MyObject p in things)
{
Children.Add(CreatePage(p));
}
}
public ContentPage CreatePage(MyObject thing) {
var emptyLabel = new Label
{
Text = "Text",
WidthRequest = 50,
HeightRequest = 50,
BackgroundColor = Color.White
};
((StackLayout)page.Content).Children.Add(emptyLabel);
var inputNumb = new Editor
{
Text=thing.Number,
TextColor = Color.Black,
HorizontalOptions = LayoutOptions.Fill,
VerticalOptions = LayoutOptions.Fill,
IsVisible = true,
BackgroundColor = Color.White
};
inputNumb.SetBinding(Label.TextProperty, "Text");
inputNumb.BindingContext = thing.Number;
((StackLayout)page.Content).Children.Add(inputNumb);
}
}
I have tried to impelemnt such a event:
inputNumb.Completed += (sender, args) =>
{
inputNumb.SetBinding(Label.TextProperty, "Text");
inputNumb.BindingContext = thing.Number;
};
but it is not working. And I think because it is on same method. Also I tried to do out of method scope, by implementing such a line on CreatePage method inputCarNumb.Completed += InputCarNumb_Completed; But then when you define your variable inputNumb it doesn't recognize and I don't know how to implement in other case. I know it is very simple, but I think I miss something by doing SetBinding / BindingContext .
I solved this problem like this:
emptyLabel.SetBinding(Label.TextProperty, "Text");
emptyLabel.BindingContext = inputNumb;
Make sure your MyObject inherits from and implements INotifyPropertyChanged so that PropertyChanged fires whenever Number changes. I generally inherit from XLabs's ViewModel, and use their SetProperty method. Don't bother setting the binding in the event. But the 2nd parameter of SetBinding should be "Number" which is MyObject's property name. Also the BindingContext should = thing.
I'm having quite a few other problems with layout that require a lot of extra InvalidateLayout() calls, so I'm starting to question if I understand how RelativeLayout works.
Here's a very simple example of a UI that wants a right-aligned label:
public class MainPage : ContentPage {
public MainPage() {
var layout = new RelativeLayout();
var label = new Label() {
Text = "I want to be right-aligned."
};
layout.Children.Add(label,
Constraint.RelativeToParent((rl) => rl.Width - label.Width),
Constraint.Constant(10));
var button = new Button() {
Text = "Invalidate"
};
button.Clicked += (object sender, EventArgs e) => layout.ForceLayout();
layout.Children.Add(button,
Constraint.Constant(10),
Constraint.Constant(10));
Content = layout;
}
}
I expect this to start with the label properly aligned, but it does not align the label correctly until another layout pass is forced. By overriding methods like OnSizeRequest() in my custom control, I've determined this is because the calls to OnSizeRequest() don't happen until after the calls to the RelativeLayout's constraint lambdas. So, when the page is laid out, the label's Width is -1. When ForceLayout() is called later, the Label has had a chance to perform its layout logic and has properly set the Width property, so it gets laid out correctly.
In a larger context, I'm trying to make a button that, when clicked, fades out and a label slides into place where it was. It's to be aligned in the bottom-right corner of my layout, but I'm finding that modifying Opacity or IsVisible only inconsistently updates the layout. The only consistent behavior is RelativeLayout really likes to ask for the control's size before it gets a chance to resize itself.
Am I interpreting how to use a RelativeLayout wrong, or is this a mistake in its logic?
Delving deep into the (current) implementation of RelativeLayout, I found a truth I did not expect: it does not consult a view's GetSizeRequest() method or call its Layout() method before the constraints are calculated, because those constraints might affect the control's final size. The consequence: while the constraints are being calculated, the control's bounds reflect its old position and size.
To "fix" this, call the view's GetSizeRequest() inside constraints that need the most up-to-date size of the control:
public class MainPage : ContentPage {
public MainPage() {
var layout = new RelativeLayout();
var label = new Label() {
Text = "I want to be right-aligned."
};
Func<RelativeLayout, double> getLabelWidth = (parent) => label.GetSizeRequest(parent.Width, parent.Height).Request.Width;
layout.Children.Add(label,
Constraint.RelativeToParent((rl) => rl.Width - getLabelWidth(rl)),
Constraint.Constant(10));
var button = new Button() {
Text = "Invalidate"
};
button.Clicked += (object sender, EventArgs e) => layout.ForceLayout();
layout.Children.Add(button,
Constraint.Constant(10),
Constraint.Constant(10));
Content = layout;
}
}