my Xamarin-Application crashes without exception after i implemented the MasterDeatilPage. I sticked to a Xamarin-Tutorial (https://www.youtube.com/watch?v=K2be1RfDYK4) and i also compared my solution to the Microsoft-Docs for MasterDetailPage.
By and large I have implemented the documentation, the only diffrences are the file locations and the way i set the ItemsSource of the MasterPage. I heard about the issue, that a not set Title of the MasterPage could lead to the same problem i have, but i did specified the Title-Property.
Here is an excerpt of the Filesystem of my solution:
Here is my Code:
MasterMenuItem.cs:
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace WhaleEstimate.Models
{
public class MasterMenuItem
{
public string Title { get; set; }
public string IconSource { get; set; }
public Color BackgroundColor { get; set; }
public Type TargetType { get; set; }
public MasterMenuItem(string title, string iconSource, Color color, Type type)
{
this.Title = title;
this.IconSource = iconSource;
this.BackgroundColor = color;
this.TargetType = type;
}
}
}
MasterDetail.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WhaleEstimate.Views.Menu;assembly=WhaleEstimate"
xmlns:detailviews="clr-namespace:WhaleEstimate.Views.DetailViews;assembly=WhaleEstimate"
x:Class="WhaleEstimate.Views.Menu.MasterDetail">
<MasterDetailPage.Master>
<local:MasterPage x:Name="masterpage"/>
</MasterDetailPage.Master>
<MasterDetailPage.Detail>
<NavigationPage>
<x:Arguments>
<detailviews:InfoScreen1/>
</x:Arguments>
</NavigationPage>
</MasterDetailPage.Detail>
</MasterDetailPage>
MasterDetail.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WhaleEstimate.Models;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace WhaleEstimate.Views.Menu
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MasterDetail : MasterDetailPage
{
public MasterDetail ()
{
InitializeComponent();
masterpage.ListView.ItemSelected += OnItemSelected;
}
private void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var item = e.SelectedItem as MasterMenuItem;
if (item != null)
{
Detail = new NavigationPage((Page)Activator.CreateInstance(item.TargetType));
masterpage.ListView.SelectedItem = null;
IsPresented = false;
}
}
}
}
MasterPage.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WhaleEstimate.Models"
x:Class="WhaleEstimate.Views.Menu.MasterPage"
Title="Test Project">
<ContentPage.Content>
<StackLayout x:Name="MasterStack" VerticalOptions="FillAndExpand">
<StackLayout x:Name="TopStack">
<Label Text="TestProject App" HorizontalOptions="Center" FontSize="Large"/>
</StackLayout>
<StackLayout x:Name="MidStack" VerticalOptions="FillAndExpand">
<ListView x:Name="listView" SeparatorVisibility="None" x:FieldModifier="public">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="{Binding BackgroundColor}">
<Image Source="{Binding IconSource}" Margin="0,10,0,10"/>
<Label Grid.Column="1" Text="{Binding Title}" TextColor="Black" FontSize="Medium"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
<StackLayout x:Name="BottomStack" VerticalOptions="EndAndExpand">
<Button Text="Do some"/>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
MasterPage.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WhaleEstimate.Models;
using WhaleEstimate.Views.DetailViews;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace WhaleEstimate.Views.Menu
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MasterPage : ContentPage
{
public ListView ListView { get { return ListView; } }
public List<MasterMenuItem> items;
public MasterPage ()
{
InitializeComponent ();
SetItems();
}
private void SetItems()
{
items = new List<MasterMenuItem>();
items.Add(new MasterMenuItem("InfoScreen1", "maus.jpg", Color.White, typeof(InfoScreen1)));
items.Add(new MasterMenuItem("InfoScreen2", "maus.jpg", Color.White, typeof(InfoScreen2)));
ListView.ItemsSource = items;
//listView.ItemsSource = items;
}
}
}
I had to return the ListView, that is defined in xaml and not in code-behind-file. So the clue was changing the line public ListView ListView { get { return ListView; } } to public ListView ListView { get { return listView; } }
Related
UPDATE 1:
The HoldingsValue calculation works if I use a local HoldingsAmount value. How do I get it to take the value from the xaml HoldingsAmount Entry?
View: TickerView.xaml
Added binding to the text property of the entry control:
<Entry Placeholder="Enter holdings"
Text="{Binding Ticker.HoldingsAmount}"
Keyboard="Numeric" />
Models: Tickers.cs
Added HoldingsAmount to the model:
namespace MVVMDemo2.MVVM.Models
{
public class Ticker
{
public decimal HoldingsAmount { get; set; }
public string AskPrice { get; set; }
public string BidPrice { get; set; }
public string HoldingsValue { get; set; }
}
}
ViewModels: TickerViewModel.cs
using Binance.Net.Clients;
using Binance.Net.Objects;
using MVVMDemo2.MVVM.Models;
using PropertyChanged;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMDemo2.MVVM.ViewModels
{
[AddINotifyPropertyChangedInterface]
public class TickerViewModel
{
public Ticker Ticker { get; set; }
//public decimal HoldingsAmount { get; set; }
public TickerViewModel()
{
var socketClient = new BinanceSocketClient(new BinanceSocketClientOptions { });
socketClient.SpotStreams.SubscribeToBookTickerUpdatesAsync("BTCUSDT", data => {
decimal HoldingsAmount = 2;
Ticker = new Ticker
{
AskPrice = $"${data.Data.BestAskPrice.ToString("N2")}",
BidPrice = $"${data.Data.BestBidPrice.ToString("N2")}",
HoldingsValue = (HoldingsAmount * data.Data.BestBidPrice).ToString()
};
});
socketClient.UnsubscribeAllAsync();
}
}
}
ORIGINAL:
I'm trying to learn MAUI and MVVM, but I can't seem to read a value from the XAML form to use with data from a WebSocket.
I currently have the following which works:
Views: TickerView.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MVVMDemo2.MVVM.Views.TickerView"
Title="TickerView">
<VerticalStackLayout
VerticalOptions="Center"
HorizontalOptions="Center">
<Label
Text="Holdings"
FontSize="25" />
<Entry Placeholder="Enter holdings"
BackgroundColor="Gray"
Keyboard="Numeric" />
<Label
Text="Ask price"
FontSize="25" />
<Label
Text="{Binding Ticker.AskPrice}"
FontSize="50" />
<Label
Text="Bid price"
FontSize="25" />
<Label
Text="{Binding Ticker.BidPrice}"
FontSize="50" />
<Label
Text="Holdings value"
FontSize="25" />
<Label
Text="{Binding Ticker.HoldingsValue}"
FontSize="50" />
</VerticalStackLayout>
</ContentPage>
Views: TickerView.xaml.cs
using MVVMDemo2.MVVM.Models;
using MVVMDemo2.MVVM.ViewModels;
namespace MVVMDemo2.MVVM.Views;
public partial class TickerView : ContentPage
{
public TickerView()
{
InitializeComponent();
BindingContext = new TickerViewModel();
}
}
Models: Ticker.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMDemo2.MVVM.Models
{
public class Ticker
{
public string AskPrice { get; set; }
public string BidPrice { get; set; }
}
}
ViewModels: TickerViewModel.cs
using Binance.Net.Clients;
using Binance.Net.Objects;
using MVVMDemo2.MVVM.Models;
using PropertyChanged;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMDemo2.MVVM.ViewModels
{
[AddINotifyPropertyChangedInterface]
public class TickerViewModel
{
public Ticker Ticker { get; set; }
public TickerViewModel()
{
var socketClient = new BinanceSocketClient(new BinanceSocketClientOptions { });
socketClient.SpotStreams.SubscribeToBookTickerUpdatesAsync("BTCUSDT", data => {
Ticker = new Ticker
{
AskPrice = $"${data.Data.BestAskPrice.ToString("N2")}",
BidPrice = $"${data.Data.BestBidPrice.ToString("N2")}",
};
});
socketClient.UnsubscribeAllAsync();
}
}
}
I want to be able to read the value from the first Entry, so I can perform the following calculation:
BestAskPrice x HoldingsAmount
Once that calculation has been done, it should constantly update the xaml view similar to how the websocket constantly the AskPrice and BidPrice in the view in realtime.
In other words, I have figured out how to get value from a websocket and display it in a xaml view, but I can';t seem to figure out how to get data from a xaml view, process it with data from a websocket, and then in realtime display the data in the xaml view.
Can anyone help?
Solution using MVVM (you need to apply so many changes):
First you need to install nuget package CommunityToolkit.MVVM
Models: Ticker.cs
namespace MVVMDemo2.MVVM.Models
{
public class Ticker
{
public string AskPrice { get; set; }
public string BidPrice { get; set; }
public string HoldingsValue { get; set; }
}
}
Views: TickerView.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MVVMDemo2.MVVM.Views.TickerView"
Title="TickerView">
<VerticalStackLayout
VerticalOptions="Center"
HorizontalOptions="Center">
<Label
Text="Holdings"
FontSize="25" />
<Entry Placeholder="Enter holdings"
BackgroundColor="Gray"
Text="{Binding HoldingsAmount}"
Keyboard="Numeric" />
<Label
Text="Ask price"
FontSize="25" />
<Label
Text="{Binding Ticker.AskPrice}"
FontSize="50" />
<Label
Text="Bid price"
FontSize="25" />
<Label
Text="{Binding Ticker.BidPrice}"
FontSize="50" />
<Label
Text="Holdings value"
FontSize="25" />
<Label
Text="{Binding Ticker.HoldingsValue}"
FontSize="50" />
</VerticalStackLayout>
Views: TickerView.xaml.cs
using MVVMDemo2.MVVM.Models;
using MVVMDemo2.MVVM.ViewModels;
namespace MVVMDemo2.MVVM.Views;
public partial class TickerView : ContentPage
{
public TickerView(TickerViewModel vm)
{
InitializeComponent();
BindingContext = vm;
}
}
ViewModels: TickerViewModel.cs
using Binance.Net.Clients;
using Binance.Net.Objects;
using MVVMDemo2.MVVM.Models;
using PropertyChanged;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMDemo2.MVVM.ViewModels
{
public partial class TickerViewModel : ObservableObject
{
[ObservableProperty]
public Ticker ticker;
[ObservableProperty]
public decimal holdingsAmount;
public TickerViewModel()
{
var socketClient = new BinanceSocketClient(new BinanceSocketClientOptions { });
socketClient.SpotStreams.SubscribeToBookTickerUpdatesAsync("BTCUSDT", data => {
Ticker = new Ticker
{
AskPrice = $"${data.Data.BestAskPrice.ToString("N2")}",
BidPrice = $"${data.Data.BestBidPrice.ToString("N2")}",
HoldingsValue = (HoldingsAmount * data.Data.BestBidPrice).ToString()
};
});
socketClient.UnsubscribeAllAsync();
}
}
}
Explanation: using OBSERVABLE PROPERTIES you will be able to see the changed between your ui and apply them on the viewmodel. it's tested and got it working on my emulator:
i recommend you to follow James Montemagno and Gerald Versluis that explains everything on their videos about bindable properties, observable objects/properties and everything about maui!
UPDATE if you get a blank page:
App.xaml.cs
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
AppShell.xaml:
<Shell
x:Class="MVVMDemo2.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MVVMDemo2.MVVM.Views">
<ShellContent
ContentTemplate="{DataTemplate local:TickerView}"
Route="TickerView" />
</Shell>
AppShell.xaml.cs
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute(nameof(TickerView), typeof(TickerView));
}
You have to bind the property in your ViewModel same as the labels
Kindly go through this for more in-depth info
https://www.c-sharpcorner.com/UploadFile/3d39b4/getting-data-from-view-to-controller-in-mvc/
I've been trying to learn Xamarin with MVVM and I'm still struggling.
I've had issues mainly trying to output information from a JSON file in a ListView.
If I just ignore MVVM and add the code directly into the View, it works perfectly.
However, when I try to use the code in the ViewModel, it can't find the binded Itemssource.
The code:
ListPageVM
using SaveUp.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using SaveUp.View;
using System.Reflection;
using System.Text;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace SaveUp.ViewModel
{
public class ListPageVM : INotifyPropertyChanged
{
private ObservableCollection<MainModel> data;
public ListPageVM()
{
var assembly = typeof(ListPageVM).GetTypeInfo().Assembly;
Stream stream = assembly.GetManifestResourceStream("SaveUp.eintraege.json");
using (var reader = new StreamReader(stream))
{
var json = reader.ReadToEnd();
List<MainModel> dataList = JsonConvert.DeserializeObject<List<MainModel>>(json);
data = new ObservableCollection<MainModel>(dataList);
lw.ItemsSource = data;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ListPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SaveUp.View.ListPage"
xmlns:viewModel="clr-namespace:SaveUp.ViewModel"
x:DataType="viewModel:ListPageVM">
<ContentPage.BindingContext>
<viewModel:ListPageVM/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<ListView x:Name="lw"
Footer="">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Geld}" Detail="{Binding Detail}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
eintraege.json
[
{
"Geld": 500.00,
"Detail": "Kaffee"
},
{
"Geld": 250.00,
"Detail": "Creme"
},
{
"Geld": 100.00,
"Detail": "Yogurt"
}
]
first, this needs to have a public property
private ObservableCollection<MainModel> data;
should look like
private ObservableCollection<MainModel> data;
public ObservableCollection<MainModel> Data {
get
{
return data;
{
set
{
data = value;
OnPropertyChanged();
}
}
if you are using MVVM, then your VM doesn't directly interact with your view
// get rid of this
lw.ItemsSource = data;
then in your XAML use binding to set the ItemsSource
<ListView ItemsSource="{Binding Data}" ...
I need help trying to figure out why my collection-view is not displaying the data that its binded to. When I run the application in debug mode the data is being populated into the Viewmodel and binded. When I go to the View.xaml and hover over the source where its binded, it displays.
I have provided the Model, ModelView, View and the code behind for the View and even a screen shot of the view running in the debugger showing that the bind seems to be working.
I have been stuck for a while any help will be truly appreciated.
What I see when I run in debug mode showing the view model is binded but just not showing.
ContactsPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Project_contact.Views.ItemsPage"
Title="{Binding Title}"
x:Name="BrowseItemsPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Clicked="AddItem_Clicked" />
</ContentPage.ToolbarItems>
<RefreshView IsRefreshing="{Binding IsBusy, Mode=TwoWay}" Command="{Binding LoadDataCommand}">
<StackLayout>
<Label x:Name="TopBanner" Text="Welcome Please wait..." />
<StackLayout Orientation="Horizontal">
<Label Text= "{Binding StringFormat='Welcome You have' }" />
</StackLayout>
<CollectionView x:Name="ItemsCollectionView2"
ItemsSource="{Binding Contacts}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10">
<Label Text="{Binding name}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16" />
<Label Text="{Binding desc}"
d:Text="Item descripton"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemDetailTextStyle}"
FontSize="13" />
<StackLayout.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Tapped="OnContactSelected_Tapped"></TapGestureRecognizer>
<SwipeGestureRecognizer Direction="Left" />
</StackLayout.GestureRecognizers>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</RefreshView>
</ContentPage>
ContactsPage.xaml.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Project_Contact.Models;
using Project_Contact.Views;
using Project_Contact.ViewModels;
using Project_Contact.Services;
using System.Data;
namespace Project_Contact.Views
{
// Learn more about making custom code visible in the Xamarin.Forms previewer
// by visiting https://aka.ms/xamarinforms-previewer
[DesignTimeVisible(false)]
public partial class ItemsPage : ContentPage
{
public ContactsViewModel viewModel { get; set; }
public ContactStore contactStore { get; set; }
public ContactsPage()
{
contactStore = new ContactStore(DependencyService.Get<Database>());
viewModel = new ContactsViewModel(contactStore);
viewModel.LoadDataCommand.Execute(null);
BindingContext = viewModel;
InitializeComponent();
}
async void OnItemSelected(object sender, EventArgs args)
{
var layout = (BindableObject)sender;
var item = (Item)layout.BindingContext;
await Navigation.PushAsync(new ItemDetailPage(new ItemDetailViewModel(item)));
}
async void AddItem_Clicked(object sender, EventArgs e)
{
await Navigation.PushModalAsync(new NavigationPage(new NewContactPage(contactStore)));
}
protected override void OnAppearing()
{
base.OnAppearing();
viewModel.LoadDataCommand.Execute(true);
}
async void OnContactSelected_Tapped(object sender, EventArgs e)
{
var layout = (BindableObject)sender;
var contact = (Contact)layout.BindingContext;
await Navigation.PushAsync(new ContactDetailPage(new ContactDetailViewModel(contactStore,contact)));
}
}
}
ContactsPageViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading.Tasks;
using Xamarin.Forms;
using Project_contact.Models;
using Project_contact.Views;
using Project_contact.Services;
namespace Project_contact.ViewModels
{
public class ContactsViewModel : BaseViewModel
{
public ObservableCollection<Contact> Contacts { get; set; } = new ObservableCollection<Contact>();
public Command LoadContacts { get; private set; }
public Command LoadDataCommand { get; private set; }
// public Command load
public ContactStore contactStore;
public int numberofContacts { get; set; }
public string TopBannerText { get; set; }
public ContactsViewModel(ContactStore contactStore)
{
Title = "Browse";
this.contactStore = contactStore;
LoadDataCommand = new Command(async () => await ExecuteLoadDataCommand());
}
public async Task ExecuteLoadDataCommand()
{
Contacts = new ObservableCollection<Contact>(await contactStore.GetContactsAsync());
LoadContacts = new Command(async () => await ExecuteLoadContactsCommand());
TopBannerText = String.Format("Welcome you have {0} contacts ",numberofContacts);
}
async Task ExecuteLoadContactsCommand()
{
if (!IsBusy)
{
IsBusy = true;
try
{
if (Contacts.Count > 0)
{
Contacts.Clear();
numberofContacts = 0;
}
var contacts = await contactStore.GetContactsAsync();
foreach (var contact in contacts)
{
Contacts.Add(contact);
numberofContacts++;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
}
}
}
Contact.cs
using System;
using System.Collections.Generic;
using System.Text;
using SQLite;
namespace Project_Contact.Models
{
public class Contact
{
[PrimaryKey, AutoIncrement]
public int id { get; set; }
public string name { get; set; }
public string desc { get; set; }
public string number {get; set;}
}
}
i'm new in developing app with xamarin. I'm developing my first crossplatform app using xamarin forms. If you can give me the simpliest solution for my problem with examples.
i have a MainPage.xaml with some stacklayout. in one of this stacklayout i want to display a local page.html using a Webview (i think).
thanks you
my page xaml is this
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="App1.info"
Title="WebView">
<ContentPage.Content>
<StackLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<WebView x:Name="webview" Source="{Binding mySource}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
my page.xaml.cs is this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace App1
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class info : ContentPage
{
public HtmlWebViewSource mySource { get; set; }
public info()
{
InitializeComponent();
BindingContext = this;
mySource = new HtmlWebViewSource
{
Html = #"<html><body>
<h1>Xamarin.Forms</h1>
<p>Welcome to WebView.</p>
</body></html>"
};
}
}
}
in xaml
<StackLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<WebView x:Name="webview" Source="{Binding mySource}" />
</StackLayout>
in code behind or ViewModel
public HtmlWebViewSource mySource { get; set; }
public MainPage()
{
InitializeComponent();
webview.Source = "#"<html><body>
<h1>Xamarin.Forms</h1>
<p>Welcome to WebView.</p>
</body></html>";";
}
Or you can use data binding(MVVM) .
I'm very new to Xamarin and C#. So if What I am asking is rookie I apologize. But I have scoured the interwebz and Stack Overflow looking for why what I am doing isn't working and can't figure it out. As far as I can tell it should be working fine but maybe/hopefully I'm just missing something simple.
I'm using MVVM (mostly) and I have a ListView made up of objects called MobileBoardUser. That List View is set up like this
<ListView
ItemsSource="{Binding BoardUsers}"
HasUnevenRows="True"
ItemSelected="StatusBoardPageListView_ItemSelected" >
<ListView.ItemTemplate >
<DataTemplate>
<ViewCell>
//then the various StackLayout and Label objects etc.
In the code behind I am trying to use the ItemSelected method to pass the selected Item into a new page where all of it's properties will be displayed.
private void StatusBoardPageListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null)
{
return;
}
MobileBoardUser userSelected = e.SelectedItem as MobileBoardUser;
Navigation.PushAsync(new BoardUserPage(userSelected));
}
The BoardUserPage Code Behind looks like this
using EIOBoardMobile.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace EIOBoardMobile.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class BoardUserPage : ContentPage
{
public class UserProp
{
public string userprop { get; set; }
}
public class UserValue
{
public string uservalue { get; set; }
}
public ObservableCollection<UserProp> SelectedUserProps { get; set; } = new ObservableCollection<UserProp>();
public ObservableCollection<UserValue> SelectedUserValues { get; set; } = new ObservableCollection<UserValue>();
public BoardUserPage(MobileBoardUser selectedUser)
{
InitializeComponent();
BindingContext = this;
MobileBoardUser shownUser = selectedUser;
foreach (var prop in shownUser.GetType().GetProperties())
{
if (prop.GetType() == typeof(String))
{
UserProp NewUserProp = new UserProp
{
userprop = prop.Name.ToString()
};
SelectedUserProps.Add(NewUserProp);
}
}
foreach (var prop in shownUser.GetType().GetProperties())
{
if (prop.GetType() == typeof(String))
{
UserValue NewUserValue = new UserValue
{
uservalue = prop.GetValue(shownUser, null).ToString()
};
SelectedUserValues.Add(NewUserValue);
}
}
}
}
}
As you can see I have created two lists of objects, one to represent the property names and one to represent the actual values of those properties so they can be used in the xaml. In production these will be dynamic so it is important I be able to do it this way. To this end the BoardUserPage xaml looks like this
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:EIOBoardMobile.Views"
x:Class="EIOBoardMobile.Views.BoardUserPage">
<ContentPage.Content>
<StackLayout Padding="20">
<ListView
ItemsSource="{Binding SelectedUserProps}"
HasUnevenRows="True" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" >
<Label Text="{Binding userprop}" HorizontalOptions="StartAndExpand" TextColor="Black" />
<ListView ItemsSource="{Binding SelectedUserValues}" HorizontalOptions="EndAndExpand" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding uservalue}" HorizontalOptions="EndAndExpand" TextColor="Blue" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
All of this compiles and I get no unhandled exceptions or run time errors. The code behind that passes the SelectedItem as MobileBoardUser into the new page works to navigate to BoarduserPage but when I get there the page is empty and doing nothing.
What have I done wrong?
Ok after some trial and error I was actually able to figure this out. I had to make some changes to the code. The typeof statements were not constructed properly. For the SelectedUserProps I was getting the typeof the property rather the value. So I had to change that. Also the nested ListView inside another ListView was causing exceptions and failing to generate. Passing e.SelectedItem after casting actually DID work. It was the foreach comparison statements that were causing me grief. So the major changes I made were to the BoardUserPage code behind and the BoardUserPage xaml. Here are those changes. Primarily using one ObservableCollection instead of two (hence now only one foreach statement and correcting the type comparison so that I was comparing values rather than the properties themselves to typeof(String). Here is the code behind
using EIOBoardMobile.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace EIOBoardMobile.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class BoardUserPage : ContentPage
{
public class UserProp
{
public string userprop { get; set; }
public string uservalue { get; set; }
}
public ObservableCollection<UserProp> SelectedUserProps { get; set; } = new ObservableCollection<UserProp>();
public BoardUserPage(MobileBoardUser selectedUser)
{
InitializeComponent();
BindingContext = this;
foreach (var prop in selectedUser.GetType().GetProperties())
{
if (prop.GetValue(selectedUser).GetType() == typeof(String))
{
UserProp NewUserProp = new UserProp
{
userprop = prop.Name.ToString(),
uservalue = prop.GetValue(selectedUser).ToString()
};
SelectedUserProps.Add(NewUserProp);
}
}
}
}
}
and here is the View (xaml)
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:EIOBoardMobile.Views"
x:Class="EIOBoardMobile.Views.BoardUserPage">
<StackLayout Padding="20" >
<ListView
x:Name="Parent"
ItemsSource="{Binding SelectedUserProps}"
HasUnevenRows="True" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" Padding="10" HeightRequest="100">
<Label Text="{Binding userprop}" HorizontalOptions="StartAndExpand" VerticalOptions="StartAndExpand" TextColor="Black" />
<Label Text="{Binding uservalue}" HorizontalOptions="EndAndExpand" VerticalOptions="EndAndExpand" TextColor="Blue" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
This displays the property names on the left and values on the right of only those properties which are strings. This was necessary to avoid displaying IDs and othe integer based key values from the database that would just be meaningless clutter to end users.