I'm trying to create some custom controls for Xamarin forms...
The controls should behave as a group of other controls. For example Label + Entry as one control to be inserted in a page.
I created a simple control here inherting from the class View.
This is an example of class with two labels. And inside this class, other controls are supposed to be inserted in addition to these labels.
public class Ton : View
{
public Ton() : base()
{
Label L;
L = new Label();
L.Parent = this;
L.Text = "XXX";
Label M;
M = new Label();
M.Parent = this;
M.Text = "YYY";
}
}
I insert then this class in a page :
<local:Ton></local:Ton>
but the contol is not shown in the page.
Does anyone know the reason please ?
Thanks.
Cheers,
A view is an abstract class and it cannot not have children. Also it does not know how to layout it's children inside it. You must have inherited your class from Layout<View> or someother existing layout classes like Grid, StackLayout, RelativeLayout, etc..
The base class depends on how you need to layout your children.
Refer my below GitHub repository to know how to create a custom control.
https://github.com/harikrishnann/BusyButton-XamarinForms
you create two Labels but don't actually add them to the View hierarchy
public class Ton : ContentView
{
public Ton() : base()
{
Label L;
L = new Label();
L.Text = "XXX";
Label M;
M = new Label();
M.Text = "YYY";
// because you have multiple elements, they must be contained in a Layout
var stack = new StackLayout();
stack.Children.Add(L);
stack.Children.Add(M);
// assign your controls to the View hierarchy
this.Content = stack;
}
}
Related
Our Xamarin.Forms app works online and offline by downloading an original database to the cell phone and then syncing the SQLite database with the online database.
Our users need a way to see if they are online and if the changes they made got uploaded to the online database. What I try to achieve is to show the sync status at the top of every ContentPage, so the users can see this information all the time while working with the app.
What I tried is this: create a class "SyncInfoContentPage" that inherits from ContentPage. All ContentPages I already wrote will now not inherit from ContentPage anymore but from SyncInfoContentPage.
The SyncInfoContentPage automatically takes its Content and replaces it with a new Stacklayout that includes the SyncInfo and the original content. By doing this I don't have to rewrite the 77 ContentPages we already have.
This code works fine on Android, but on iOS the SyncInfo is not visible and (even worse) my ContentPages that inherit from SyncInfoContentPage do not react to anything anymore.
Here is my code:
public class SyncInfoContentPage : ContentPage
{
private readonly Frame SyncInfo;
public SyncInfoContentPage()
{
SyncInfo = BuildSyncInfo(); //Creates the frame with the sync Information
PropertyChanged += SyncInfoContentPage_PropertyChanged;
}
private void SyncInfoContentPage_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Add the SyncInfo Frame on top of the Content when the Content gets changed
if (e.PropertyName.Equals("Content"))
{
bool change = false;
// If the content already is a StackLayout, check if the SyncInfo already got added, so that theres no infinite loop.
if (Content is StackLayout)
{
var check = Content as StackLayout;
if (!check.Children.Contains(SyncInfo))
{
change = true;
}
}
else // if the Content is no StackLayout, the SyncInfo Frame can't be inside the Content yet
{
change = true;
}
if (change)
{
var layout = Content; // This is a reference, probably the error?
Device.BeginInvokeOnMainThread(() =>
{
Content = new StackLayout
{
Children = { SyncInfo, layout }
};
});
}
}
}
}
The problem is probably that iOS doesn't like this part:
var layout = Content;
Content = new StackLayout { Children = { SyncInfo, layout } };
Thanks in advance for your help and any suggestions :-)
I got it to work. The solution is simple but strange. You have to add the original Content after you added the SyncInfo status bar.
var layoutOld = Content;
var layoutNew = new StackLayout
{
Children = { SyncInfo }
};
Content = layoutNew;
layoutNeu.Children.Add(layoutOld);
I've created some pages
this.content1 = new DetailPage("ContentPage1");
this.content2 = new DetailPage("ContentPage2");
and I have defined a field
private View detailView;
with the following layout
Content = new StackLayout
{
Padding = new Thickness(0, Device.OnPlatform<int>(20, 0, 0), 0, 0),
Orientation = StackOrientation.Horizontal,
Children = {
buttonContainer,
this.detailView,
},
};
On a button click the detailView should be exchanged
private void Button1_Clicked(object sender, EventArgs e)
{
this.detailView = this.content1.Content;
}
The click event is called, but the view isn't updated. Is this the wrong way to exchange a "subview" in a container? How is this done?
You need to remove the current detailView from the Children collection and then add your new view afterward. Simply swapping the value of detailView will not affect the visual UI.
If I am understanding the context of your code snippets correctly, then this should resolve the problem in your Button1_Clicked handler:
((StackLayout) Content).Children.Remove(this.detailView);
this.detailView = this.content1.Content;
((StackLayout) Content).Children.Add(this.detailView);
Currently with ListView's I've only found that you can create a template for cells, which makes each cell look exactly the same. You can't have multiple custom cells in the listview. There are work-arounds like hiding the content in the cell depending on the content, but this seems pretty hacky.
The reason I want to use a listview over a tableview is because we plan on doing inserts, deletions, dynamically showing certain cells, and listview's can be binded to a data source.
Create your own ViewCell which overrides binding context change method. When the binding changes set the ViewCell's view to one that matches the type of view model and also set the height of the cell. Below is a quick sample that should give you an idea how to accomplish it.
public class DataTemplateCell1 : ViewCell
{
protected override void OnBindingContextChanged()
{
var vm1 = this.BindingContext as ViewModel1;
if (vm1 != null)
{
this.View = new View1() { HeightRequest = 40 };
this.Height = this.View.HeightRequest;
return;
}
var vm2 = this.BindingContext as ViewModel2;
if (vm2 != null)
{
this.View = new View2() { HeightRequest = 80 };
this.Height = this.View.HeightRequest;
return;
}
base.OnBindingContextChanged();
}
}
I've created a master detail page on the left side using Xamarin.Forms, how about creating the same for the right side?
Below is my sample code for the left slider menu;
public class App
{
static MasterDetailPage MDPage;
public static Page GetMainPage()
{
return MDPage = new MasterDetailPage {
Master = new ContentPage {
Title = "Master",
BackgroundColor = Color.Silver,
Icon = Device.OS == TargetPlatform.iOS ? "menu.png" : null,
Content = new StackLayout {
Padding = new Thickness(5, 50),
Children = { Link("A"), Link("B"), Link("C") }
},
},
Detail = new NavigationPage(new ContentPage {
Title = "A",
Content = new Label { Text = "A" }
}),
};
}
static Button Link(string name)
{
var button = new Button {
Text = name,
BackgroundColor = Color.FromRgb(0.9, 0.9, 0.9)
};
button.Clicked += delegate {
MDPage.Detail = new NavigationPage(new ContentPage {
Title = name,
Content = new Label { Text = name }
});
MDPage.IsPresented = false;
};
return button;
}
}
This does not exists in the Xamarin.Forms controls set, but you can create your own, with renderers for each platform.
You'll find the required information on http://developer.xamarin.com/guides/cross-platform/xamarin-forms/custom-renderer/
Solution here
this is now supported in xamarin forms 3.0 and up, NO custom renderers
needed !! or third party libraries.
the trick is to force the layout to RTL,
while flow direction works, its hard to make the top nav bar to follow,
below is the solution for that problem, it works... let me know if you face any issues on older ios versions IOS8 and less.
xamarin forms RTL master details page with icon RTL/LTR hamburger
You can make it by ToolbarItems in Xamarin Forms
ToolbarItems will appear in right side.
You can find more info from the following links :
http://codeworks.it/blog/?p=232
I have a search form that opens in a com.smartgwt.client.widgets.Window.Window(). In it, I have a VLayout, in which I have a search form:
DynamicForm search = new DynamicForm();
// setMargin, setTitle, setNumCols
TextItem name = new TextItem();
name.setFormatOnFocusChange(true);
//setEditorValueFormatter, etc.
search.setFields(/*some fields*/, name, /*other fields*/);
name.focusInItem();
And the focus is not in the item (it's nowhere). Why is that so?
Thank you in advance!
EDIT:
Here is the code of the two Mediators:
public class MainMediator extends Mediator {
private Window popup = new Window();
protected void initView(){
// here I have a Form with fields and icon on one TextItem, on which I do:
searchField.addIconClickHandler(new IconClickHandler() {
popup = new Window();
popup.setIsModal(true);
popup.setShowModalMask(true);
});
}
public final void handleNotification(final INotification notification){
// if the right notification is sent, execute this code:
PopupMediator m = (PopupMediator) this.getFacade().retreiveMediator(PopupMediator.NAME);
VLayout popupLayout = (VLayout) m.getViewComponent();
popup.addItem(popupLayout);
popup.show();
}
}
public class PopupMediator extends Mediator {
protected void initView(){
viewComponent = new VLayout();
DynamicForm searchForm = new DynamicForm();
// searchForm props
TextItem name = new TextItem();
// name props and some other fields
searchForm.setFields(name /* and the others */);
VLayout searchFormContainer = new VLayout();
// searchFormContainer props
searchFormContainer.setMembers(seachForm);
name.focusInItem(); // not working on popup shown
HLayout searchContainer = new HLayout();
// searchContainer props
searchContainer.setMembers(grid1, searchFormContainer);
VLayout container = new VLayout();
// container props
container.setMembers (searchContainer, grid2);
((VLayout)viewComponent).setMembers(container, buttons);
}
You're getting this problem because formitem.focusInItem() works only after the formitem is drawn or say rendered in the browser. Adding the formitem in DynamicForm does not draw it.
I don't know where you're placing the DynamicForm, but to understand it completely, look at the following code:
Window window = new Window();
window.setSize("900px", "500px");
VLayout layout = new VLayout();
DynamicForm dynamicForm = new DynamicForm();
dynamicForm.setSize("800px", "400px");
TextItem item = new TextItem();
dynamicForm.setFields(item);
item.focusInItem(); // This won't work.
layout.addMember(dynamicForm);
window.addItem(layout);
item.focusInItem(); // This won't work.
window.show();
item.focusInItem(); // This will work.
So change your code accordingly.
Not sure how you receive handleNotification() callbacks, but you shouldn't use window.addItem() in it.
That will cause multiple items to be added/overwritten each time callback is called.
If handleNotification() callback is required, it should be only used for window.show(), plus any form field population/setting focus/etc.
If the content of Window is NOT going to change from one callback to another, initialize window layout during window creation.
If content of Window is GOING to change from one callback to another, you will need to remove previously added items.
Here's a simple working implementation that popup the window on a button click and set focus on a given field.
TextItem name1 = new TextItem("name1", "Name 1");
final TextItem name2 = new TextItem("name2", "Name 2"); // setting focus to name2
TextItem name3 = new TextItem("name3", "Name 3");
final DynamicForm searchForm = new DynamicForm();
// searchForm.setAutoFocus(true); // sets focus to first focusable field
searchForm.setFields(name1, name2, name3);
VLayout searchFormContainer = new VLayout();
searchFormContainer.setMembers(searchForm);
final Window window = new Window();
window.setIsModal(true);
window.setShowModalMask(true);
window.setAutoCenter(true);
window.setSize("400px", "300px");
window.addItem(searchFormContainer);
Button button = new Button("Search");
button.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
window.show();
name2.focusInItem();
// searchForm.focusInItem(name2); // this also works
}
});
Its possible to use DynamicForm.setAutoFocus to automatically focus on first focusable field in the form.
Why don't you try to focus on the form itself:
search.focus();