I am creating a page in my app that lets the users answer a series of question. I started with creating a 5 item questions for testing purposes. The answer can be from multiple choices or a string. I've stored the questions in a CarouselView, Binding the Position to qPos. By clicking the Next button, the user will proceed to the next question, and vice versa with the Prev button. I've successfully add the answer for each question in the list of objects. However, when there is a 3 items gap, the answer is set to null.
For example, I've already answered questions 1-4 and am now in question 5. But when I traverse back to question #1, the answer is null.
I've attached a gif below for better understanding of the situation.
Since I've reach question #5, answers from question #2 and #1 are reset to null.
Here are my codes:
Models
public class QuestionsModel
{
public int QuestionNumber { get; set; }
public string Question { get; set; }
public string AnswerableBy { get; set; } //Determine if the answer is from choices or not
public string Answer { get; set; } //Stores answer that are in string format
public AnswerModel ChoiceAnswer { get; set; } //Stores answer that are from choices
public ObservableRangeCollection<AnswerModel> Choices { get; set; } //List of choices if available
}
public class AnswerModel
{
public string Choice { get; set; }
}
}
ViewModel
public ObservableRangeCollection<QuestionsModel> questionList { get; set; } // Question List
public SetUpOwnerViewModel()
{
//Populate Questions
questionList = new ObservableRangeCollection<QuestionsModel>() {
new QuestionsModel()
{
QuestionNumber = 1,
Question = "What is your preferred pet to adopt?",
AnswerableBy = "Choices",
Choices = new ObservableRangeCollection<AnswerModel>()
{
new AnswerModel(){Choice = "Dog"},
new AnswerModel(){Choice = "Cat"},
new AnswerModel(){Choice = "Both"}
}
},
new QuestionsModel()
{
QuestionNumber = 2,
Question = "Have you adopted from a pet shelter before?",
AnswerableBy = "Choices",
Choices = new ObservableRangeCollection<AnswerModel>()
{
new AnswerModel(){Choice = "Yes"},
new AnswerModel(){Choice = "No"}
}
},
new QuestionsModel()
{
QuestionNumber = 3,
Question = "How many children below 18 are present in the house?",
AnswerableBy = "Numeric",
Answer = ""
},
new QuestionsModel()
{
QuestionNumber = 4,
Question = "How many other pets are in your house right now?",
AnswerableBy = "Numeric",
Answer = ""
},
new QuestionsModel()
{
QuestionNumber = 5,
Question = "Who else do you live with?",
AnswerableBy = "Choices",
Answer = "",
Choices = new ObservableRangeCollection<AnswerModel>()
{
new AnswerModel(){Choice = "Spouse"},
new AnswerModel(){Choice = "Parents"},
new AnswerModel(){Choice = "Roomate"},
new AnswerModel(){Choice = "None"}
}
}
};
nextStageComm = new AsyncCommand(nextStage);
nextQComm = new AsyncCommand(nextQ);
prevQComm = new AsyncCommand(prevQ);
}
public ICommand nextStageComm { get; }
public ICommand nextQComm { get; }
public ICommand prevQComm { get; }
public ICommand setAnswerComm { get; }
private int _stage = 1; //Stage 1: Welcome view, Stage 2: Questions View
public int stage
{
get => _stage;
set => SetProperty(ref _stage, value);
}
private int _qPos = 0;
public int qPos
{
get => _qPos;
set => SetProperty(ref _qPos, value);
}
private async Task nextStage()
{
if (_stage < 2)
stage++;
}
private async Task nextQ()
{
if (_qPos < 5)
qPos++;
}
private async Task prevQ()
{
if (_qPos > 0)
qPos--;
}
View
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodel="clr-namespace:PawAdopt_v5.ViewModels.StartUp"
xmlns:model="clr-namespace:PawAdopt_v5.Models.Misc"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Class="PawAdopt_v5.Views.StartUp.SetUp.SetUpOwnerView"
Shell.NavBarIsVisible="False">
<ContentPage.BindingContext>
<viewmodel:SetUpOwnerViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<AbsoluteLayout>
<ContentView AbsoluteLayout.LayoutBounds="0.5,0.5,0.9,0.5"
AbsoluteLayout.LayoutFlags="All"
><!--Add BG Color here-->
<ContentView.Triggers>
<DataTrigger TargetType="ContentView"
Binding="{Binding stage}"
Value="1">
<Setter Property="Content">
<Setter.Value>
<AbsoluteLayout>
<StackLayout AbsoluteLayout.LayoutBounds="0,0.3"
AbsoluteLayout.LayoutFlags="YProportional"
Spacing="20">
<Label Text="Welcome"
TextColor="{x:StaticResource PrimaryColor}"
FontSize="30"
FontAttributes="Bold"/>
<Label Text="Thank you for using Paw-Adopt. Help us set up your account."
TextColor="Black"
FontSize="14"/>
</StackLayout>
<Button Text="Next"
TextTransform="None"
BackgroundColor="Transparent"
BorderColor="{x:StaticResource SecondaryColor}"
BorderWidth="2"
CornerRadius="10"
AbsoluteLayout.LayoutBounds="1,1"
AbsoluteLayout.LayoutFlags="PositionProportional"
Command="{Binding nextStageComm}"/>
</AbsoluteLayout>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger TargetType="ContentView"
Binding="{Binding stage}"
Value="2">
<Setter Property="Content">
<Setter.Value>
<StackLayout HorizontalOptions="StartAndExpand">
<CarouselView ItemsSource="{Binding questionList}"
IsSwipeEnabled="False"
Position="{Binding qPos}">
<CarouselView.ItemTemplate>
<DataTemplate x:DataType="model:QuestionsModel">
<AbsoluteLayout>
<StackLayout>
<Label FontSize="30"
FontAttributes="Bold"
TextColor="{x:StaticResource PrimaryColor}">
<Label.FormattedText>
<FormattedString>
<Span Text="#"/>
<Span Text="{Binding QuestionNumber}"/>
</FormattedString>
</Label.FormattedText>
</Label>
<Label Text="{Binding Question}"
TextColor="Black"
FontSize="17"/>
</StackLayout>
<ContentView AbsoluteLayout.LayoutBounds="0,0.5,1,0.15"
AbsoluteLayout.LayoutFlags="All">
<ContentView.Triggers>
<DataTrigger TargetType="ContentView"
Binding="{Binding AnswerableBy}"
Value="Choices">
<Setter Property="Content">
<Setter.Value>
<Picker ItemsSource="{Binding Choices}"
ItemDisplayBinding="{Binding Choice}"
SelectedItem="{Binding ChoiceAnswer}">
</Picker>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger TargetType="ContentView"
Binding="{Binding AnswerableBy}"
Value="Numeric">
<Setter Property="Content">
<Setter.Value>
<Entry Keyboard="Numeric"
Text="{Binding Answer}"/>
</Setter.Value>
</Setter>
</DataTrigger>
</ContentView.Triggers>
</ContentView>
</AbsoluteLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<StackLayout Orientation="Horizontal">
<Button Text="Prev"
TextTransform="None"
BackgroundColor="Transparent"
BorderColor="{x:StaticResource SecondaryColor}"
BorderWidth="2"
CornerRadius="10"
HorizontalOptions="StartAndExpand"
Command="{Binding prevQComm}"/>
<Button Text="Next"
TextTransform="None"
BackgroundColor="Transparent"
BorderColor="{x:StaticResource SecondaryColor}"
BorderWidth="2"
CornerRadius="10"
HorizontalOptions="End"
Command="{Binding nextQComm}"/>
</StackLayout>
</StackLayout>
</Setter.Value>
</Setter>
</DataTrigger>
</ContentView.Triggers>
</ContentView>
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>
I realized that the answers are resetting to null after a 3-item gap by using breakpoints. Is that really how carousel view works or my methods are wrong?
Thank you in advance for your answers
I tested the code you provided and also used breakpoints. Just you said that:
I realized that the answers are resetting to null after a 3-item gap by using breakpoints.
It seems that Property Content of ContentView does not support Picker very well.
However, you can use this to achieve your need:
View
<DataTrigger TargetType="ContentView"
Binding="{Binding AnswerableBy}"
Value="Text">
<Setter Property="Content">
<Setter.Value>
<Entry Keyboard="Text"
Text="{Binding Answer}"/>
</Setter.Value>
</Setter>
</DataTrigger>
ViewModel
new QuestionsModel()
{
QuestionNumber = 1,
Question = "What is your preferred pet to adopt?",
AnswerableBy = "Text",
Answer = ""
}
Wish it could be helpful to you.
Related
xaml.cs page:
public partial class TalimatGorevEkle : ContentPage
{
TalimatGorevModelmobil Model;
public TalimatGorevEkle() {
InitializeComponent();
var kullanicilist = Methodlar.GorevliListeGetir(Sabitler.Token);
KullaniciSec.ItemsSource = kullanicilist.GorevliListe; //picker'da listelenecek kişiler
}
}
.xaml file:
<?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="OzelKalem.Views.ListeContentViews.TalimatGorevEkle">
<ContentPage.Content>
<ScrollView>
<StackLayout>
<Grid>
<StackLayout Grid.Row="0" Padding="8">
<Label Text="Görev Başlığı:" TextColor="#FF7F24" FontSize="14"></Label>
<Entry x:Name="talimatAdi" Placeholder="Görev Başlığı Giriniz" HeightRequest="60" ></Entry>
<Label Text="Görev Adı:" TextColor="#FF7F24" FontSize="14" ></Label>
<Entry x:Name="talimatGörevAdi" Placeholder="Görev Adı Giriniz" HeightRequest="60" ></Entry>
<Label Text= "Görevin Son Tarihini Seçiniz:" TextColor="#FF7F24" FontSize="14"></Label>
<DatePicker Format="dd-MM-yyyy HH:mm" x:Name="gorevSonTarih" TextColor="Black" ></DatePicker>
<Picker x:Name="KullaniciSec" Title="Kullanıcı seçiniz." ItemsSource="{Binding GorevliListeModel}" ItemDisplayBinding="{Binding GorevliKullaniciAdi}"></Picker>
<Button x:Name="SaveCourseButton" Clicked="GorevEkle" Command="{Binding}" Text="Kaydet" BackgroundColor="LightGray" TextColor="#FF7F24" Margin="3" />
</StackLayout>
</Grid>
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>
I have a list in xamarin I have it displayed in picker. But I want it to be multiple selectable. How can I write for it?
How should I select it as checkbox or over the picker?
Yes , for multi-select items, you can use
Multiple selection of CollectionView to achieve this.
And you can check the official sample here.
Of course, you can also add CheckBox to each item of the ListView to achieve this.
There is a similar thread here, you can check it here: How to create select all check box in xamarin form.
I have used collection view but I need to set the list of selected users to TaskList in my View model. Actually, I need to send all the data entered in this form to the Service using the View model.selected user
How can I assign a list to a variable?
TalimatGorevEkle.xaml.cs :
using System;
using System.Collections;
using System.Collections.Generic;
using OzelKalem.Models;
using Plugin.Toasts;
using Xamarin.Forms;
namespace OzelKalem.Views.ListeContentViews
{
public partial class TalimatGorevEkle : ContentPage
{
TalimatGorevModelmobil Model;
public TalimatGorevEkle() {
InitializeComponent();
var kullanicilist = Methodlar.GorevliListeGetir(Sabitler.Token);
CollectionView collectionView = new CollectionView
{
SelectionMode = SelectionMode.Multiple
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "GorevliListeModel");
collectionView.SelectionChanged += OnCollectionViewSelectionChanged;
collectionlist.ItemsSource = kullanicilist.GorevliListe;
collectionlist.ScrollTo(kullanicilist.GorevliListe);
}
void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var previous = e.PreviousSelection;
var current = e.CurrentSelection;
}
private void GorevEkle(object sender, EventArgs e)
{
var talimat = talimatAdi.Text;
var talimatGorev = talimatGörevAdi.Text;
var gorevSonTariih = gorevSonTarih.Date;
GorevliListeModel items = collectionlist.SelectedItems as GorevliListeModel;
}
}
}
TalimatGorevEkle.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="OzelKalem.Views.ListeContentViews.TalimatGorevEkle">
<ContentPage.Content>
<ScrollView>
<StackLayout>
<Grid>
<StackLayout Grid.Row="0" Padding="8">
<Label Text="Görev Başlığı:" TextColor="#FF7F24" FontSize="14"></Label>
<Entry x:Name="talimatAdi" Placeholder="Görev Başlığı Giriniz" HeightRequest="60" ></Entry>
<Label Text="Görev Adı:" TextColor="#FF7F24" FontSize="14" ></Label>
<Entry x:Name="talimatGörevAdi" Placeholder="Görev Adı Giriniz" HeightRequest="60" ></Entry>
<Label Text= "Görevin Son Tarihini Seçiniz:" TextColor="#FF7F24" FontSize="14"></Label>
<DatePicker Format="dd-MM-yyyy HH:mm" x:Name="gorevSonTarih" TextColor="Black" ></DatePicker>
<!-- <Picker x:Name="KullaniciSec" Title="Kullanıcı seçiniz." ItemsSource="{Binding GorevliListeModel}" ItemDisplayBinding="{Binding GorevliKullaniciAdi}"></Picker>!-->
<Label Text="Kullanıcı Seçiniz:" TextColor="#FF7F24" FontSize="14" ></Label>
<CollectionView x:Name="collectionlist"
ItemsSource="{Binding GorevliListeModel}"
SelectionMode="Multiple"
SelectionChanged="OnCollectionViewSelectionChanged"
SelectedItems="{Binding GorevliKullaniciAdi}" Margin="10" HeightRequest="150">
<CollectionView.ItemTemplate >
<DataTemplate >
<StackLayout >
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal" >
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="White" />
</VisualState.Setters>
</VisualState>
<VisualState Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="LightGray" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Label Text="{Binding GorevliKullaniciAdi}" FontSize="14" Padding="5"></Label>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Button x:Name="SaveCourseButton" Clicked="GorevEkle" Command="{Binding}" Text="Kaydet" BackgroundColor="LightGray" TextColor="#FF7F24" Margin="3" />
</StackLayout>
</Grid>
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>
Viewmodel:
public class TalimatGorevModelmobil
{
public string TalimatBaslik { get; set; }
public string TalimatGorevAdi { get; set; }
public int ID { get; set; }
public int TalimatID { get; set; }
public int Durum { get; set; }
public DateTime EklenmeTarihi { get; set; }
public int IslemKulID { get; set; }
public List<GorevliListeModel> GorevliListe { get; set; }
public DateTime SonTarih { get; set; }
public int ServisBaglantiDID { get; set; }
}
public class GorevliListeModel
{
public string GorevliKullaniciAdi { get; set; }
public string GorevliID { get; set; }
}
I have this object of class type HouseInfo that contains a list property:
public class HouseInfo
{
public string House
{
get;
set;
}
public List<String> Details
{
get;
set;
}
}
public List<HouseInfo> HouseInfos { get; set; }
I am successfully binding the House property to main items of combo box using ItemSource property in xaml but can't figure out the binding of Details to their respective submenus.
<ComboBox x:Name="Houses1"
Grid.Row="1"
Grid.Column="4"
ItemsSource="{Binding HouseInfos}"
Padding="0"
DisplayMemberPath="House"
VerticalContentAlignment="Center"
VerticalAlignment="Top"
HorizontalContentAlignment="Stretch"
Margin="0,0,0,2">
</ComboBox>
I tried customizing menuitems in xaml but I get the error "itemsCollection must be empty before using items Source."
How do I get the Details list in each menu item as submenu items?
Any help would be appreciated. Thanks in advance.
Update:
I have bound submenu items as well but they are not visible. I am sure they have bound successfully as it generates submenu items equal to the count of the list inside the details property list of the object. This is the updated xaml for the menu:
<Menu x:Name="menu"
VerticalAlignment="Top"
Grid.Row="1"
Grid.Column="4"
Height="19">
<MenuItem ItemsSource="{Binding HouseInfos}"
Padding="0"
Background="#0068FF11"
VerticalAlignment="Top"
RenderTransformOrigin="0.5,0.5"
Height="19"
Width="105">
<MenuItem.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform />
<TranslateTransform X="0.5" />
</TransformGroup>
</MenuItem.RenderTransform>
<MenuItem.Header>
<Label x:Name="headerYears"
Margin="0"
Padding="0"
Content="Houses"
Background="#00FF0000"
MaxHeight="18"
UseLayoutRounding="False"
RenderTransformOrigin="0,0"
HorizontalContentAlignment="Center" />
</MenuItem.Header>
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header"
Value="{Binding House}" />
<Setter Property="ItemsSource"
Value="{Binding InfoPoints}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
Here is the image of menu which is populated but not visible.
Bound but invisible submenu items
Try using the DataSource property of the combobox. You can assign HouseInfos.House1.
What I did was I dynamically assign them to the combobox
comboBox1.DataSource = HouseInfo.House1.Details;
comboBox1.DisplayMember = "HouseDetails";
comboBox1.ValueMember = "HouseDetailsID";
Or you can try something like the above.
Use this structure. I matched the names with your own names.
MainWindw.xaml
<Window x:Class="MyNameSpace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyNameSpace"
mc:Ignorable="d"
Title="TestMenu" Height="450" Width="800">
<DockPanel>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding MenuItems}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:HouseInfo}" ItemsSource="{Binding Path=Details}">
<TextBlock Text="{Binding House}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
<Grid>
</Grid>
</DockPanel>
</Window>
MainWindow.cs
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
namespace MyNameSpace
{
/// <summary>
/// Interaction logic for MainWindw.xaml
/// </summary>
public partial class MainWindw : Window
{
public List<HouseInfo> MenuItems { get; set; }
public MainWindw()
{
InitializeComponent();
MenuItems = new List<HouseInfo>();
HouseInfo houseInfo1 = new HouseInfo();
houseInfo1.House = "Header A";
houseInfo1.Details = new List<HouseInfo>() { new HouseInfo() { House = "Header A1" }, new HouseInfo() { House = "Header A2" } };
HouseInfo houseInfo2 = new HouseInfo();
houseInfo2.House = "Header B";
houseInfo2.Details = new List<HouseInfo>() { new HouseInfo() { House = "Header B1" }, new HouseInfo() { House = "Header B2" } };
MenuItems.Add(houseInfo1);
MenuItems.Add(houseInfo2);
DataContext = this;
}
}
public class HouseInfo
{
public string House
{
get;
set;
}
public List<HouseInfo> Details { get; set; }
private readonly ICommand _command;
public HouseInfo()
{
_command = new CommandViewModel(Execute);
}
public ICommand Command
{
get
{
return _command;
}
}
private void Execute()
{
// (NOTE: In a view model, you normally should not use MessageBox.Show()).
MessageBox.Show("Clicked at " + House);
}
}
public class CommandViewModel : ICommand
{
private readonly Action _action;
public CommandViewModel(Action action)
{
_action = action;
}
public void Execute(object o)
{
_action();
}
public bool CanExecute(object o)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
}
}
you can gave style to every element with this code
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.ItemContainerStyle>
for example add this line to HouseInfo class
public Thickness Margin { get; set; }
and MainWindow.cs
MenuItems = new List<HouseInfo>();
HouseInfo houseInfo1 = new HouseInfo();
houseInfo1.House = "Header A";
houseInfo1.Margin = new Thickness(5);
houseInfo1.Details = new List<HouseInfo>() { new HouseInfo() { House = "Header A1" }, new HouseInfo() { House = "Header A2", Margin=new Thickness(10) } };
and set Style in xaml
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="Margin" Value="{Binding Margin}" />
</Style>
</Menu.ItemContainerStyle>
test:
I have these lines of code on my XAML
<ContentPage.BindingContext>
<x:Reference Name="messagesPage" />
</ContentPage.BindingContext>
....
<Label Text="{Binding ConversationPartner.contactName[0]}" FontSize="Title" TextColor="Black"
VerticalOptions="Center" FontAttributes="Bold" HorizontalOptions="StartAndExpand">
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding ConversationPartner.contactID[1], Converter={StaticResource isViewerConverter}}" Value="False">
<Setter Property="Text" Value="{Binding ConversationPartner.contactName[1]}"/>
</DataTrigger>
</Label.Triggers>
</Label>
What I want to happen is that a name on the label represented by ConversationPartner.contactName[0] must appear on my application but it doesn't.
Here's the code behind
public partial class MessagesPage : ContentPage
{
DataClass dataClass = DataClass.GetInstance;
public ICommand CloseMsg => new Command(async () => await Navigation.PopModalAsync(true));
public ICommand SendCommand => new Command(Send);
ContactModel ConversationPartner;
public MessagesPage(ContactModel input)
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
ConversationsList = new ObservableCollection<ConversationModel>();
ConversationPartner = input;
/// some cloud firestore code here
}
}
Found a solution.
I made an observable collection so that I could bind easily.
My code behind:
public MessagesPage(ContactModel input)
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
ConversationsList = new ObservableCollection<ConversationModel>();
convoers = new ObservableCollection<string> {input.contactName[0], input.contactName[1], input.contactID[1] };
ConversationPartner = input;
...
}
then in my XAML,
<Label Text="{Binding convoers[0]}" FontSize="Title" TextColor="Black"
VerticalOptions="Center" FontAttributes="Bold" HorizontalOptions="StartAndExpand">
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding convoers[2], Converter={StaticResource isViewerConverter}}" Value="False">
<Setter Property="Text" Value="{Binding convoers[1]}"/>
</DataTrigger>
</Label.Triggers>
</Label>
I have an observable collection that I am displaying in a Xamarin Forms ListView. I have defined a detail and a summary template that I use to view each list item. I want to be able to dynamically change between summary and detail template based on a Boolean property in each item.
Here is the item.
public class MyItem : INotifyPropertyChanged
{
bool _switch = false;
public bool Switch
{
get
{
return _switch;
}
set
{
if (_switch != value)
{
_switch = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Switch"));
}
}
}
public int Addend1 { get; set; }
public int Addend2 { get; set; }
public int Result
{
get
{
return Addend1 + Addend2;
}
}
public string Summary
{
get
{
return Addend1 + " + " + Addend2 + " = " + Result;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Here is the observable collection. Note that whenever the switch value changes I remove the item and reinsert. The reason this is done is to force the ListView to reselect the DataTemplate.
public class MyItems : ObservableCollection<MyItem>
{
protected override void InsertItem(int index, MyItem item)
{
item.PropertyChanged += MyItems_PropertyChanged;
base.InsertItem(index, item);
}
protected override void RemoveItem(int index)
{
this[index].PropertyChanged -= MyItems_PropertyChanged;
base.RemoveItem(index);
}
private void MyItems_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
int index = IndexOf(sender as MyItem);
if(index >= 0)
{
RemoveAt(index);
Insert(index, sender as MyItem);
}
}
}
Here is my data template selector...
public class MyItemTemplateSelector : DataTemplateSelector
{
DataTemplate Detail { get; set; }
DataTemplate Summary { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
if(item is MyItem)
{
return (item as MyItem).Switch ? Detail : Summary;
}
return null;
}
}
Here are my resource definitions...
<DataTemplate x:Key="MyDetail">
<ViewCell>
<StackLayout Orientation="Horizontal">
<Switch IsToggled="{Binding Switch}"/>
<Entry Text="{Binding Addend1}"/>
<Entry Text="{Binding Addend2}"/>
<Label Text="{Binding Result}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="MySummary">
<ViewCell>
<StackLayout Orientation="Horizontal">
<Switch IsToggled="{Binding Switch}"/>
<Label Text="{Binding Summary}" VerticalOptions="Center"/>
</StackLayout>
</ViewCell>
</DataTemplate>
<local:MyItemTemplateSelector x:Key="MySelector" Detail="{StaticResource MyDetail}" Summary="{StaticResource MySummary}"/>
Here is my collection initialization...
MyItems = new MyItems();
MyItems.Add(new MyItem() { Switch = true, Addend1 = 1, Addend2 = 2 });
MyItems.Add(new MyItem() { Switch = false, Addend1 = 1, Addend2 = 2 });
MyItems.Add(new MyItem() { Switch = true, Addend1 = 2, Addend2 = 3 });
MyItems.Add(new MyItem() { Switch = false, Addend1 = 2, Addend2 = 3 });
And this is what it looks like...
Right. So everything works fine. If the switch is toggled the view of the item changes from summary to detail. The problem is that this cannot be the right way of doing this! It is a complete kluge to remove a list item and put it back in the same place in order to get the data template to reselect. But I cannot figure out another way of doing it. In WPF I used a data trigger in an item container style to set the content template based on the switch value, but there seems to be no way to do the equivalent thing in Xamarin.
The way to do this is not through switching templates, but defining a content view as the template and changing the visibility of controls within the template. There is apparently no way to get the ListView to re-evaluate the item template on an item short of removing it and re-adding it.
Here is my content view...
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamarinFormsBench"
x:Class="XamarinFormsBench.SummaryDetailView">
<ContentView.Content>
<StackLayout x:Name="stackLayout" Orientation="Horizontal">
<Switch x:Name="toggle" IsToggled="{Binding Switch}"/>
<Entry x:Name="addend1" Text="{Binding Addend1}"/>
<Entry x:Name="addend2" Text="{Binding Addend2}"/>
<Label x:Name="result" Text="{Binding Result}"/>
<Label x:Name="summary" Text="{Binding Summary}" VerticalOptions="Center"/>
</StackLayout>
</ContentView.Content>
This is the code behind...
namespace XamarinFormsBench
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SummaryDetailView : ContentView
{
public SummaryDetailView()
{
InitializeComponent();
toggle.PropertyChanged += Toggle_PropertyChanged;
UpdateVisibility();
}
private void Toggle_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == "IsToggled")
{
UpdateVisibility();
}
}
private void UpdateVisibility()
{
bool isDetail = toggle.IsToggled;
addend1.IsVisible = isDetail;
addend2.IsVisible = isDetail;
result.IsVisible = isDetail;
summary.IsVisible = !isDetail;
InvalidateLayout(); // this is key!
}
}
}
Now the main page contains this...
<ListView ItemsSource="{Binding MyItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<local:SummaryDetailView/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The key to making this work properly is to invalidate the layout of the ContentView when switching between summary and detail. This forces the ListView to layout the cell again. Without this the controls that are made invisible disappear the controls made visible never show. You do not need this if the ContentView is used outside of the ListView. This seems to me to be a bug in the ListView. You could get the item template switching to work if you could invalidate the layout of the ViewCell, but there is no public method (only a protected one) to do this.
This was tricky issue for me few years ago. I've came to MarkupExtensions and converters (IValueConverter). After heavy struggle with XAML extensions realm I've figured an obvious thing: it shouldn't be done like that.
For dynamic change of (m)any property(ies) of the component you should use Styles. Reactions of property (it has to be DependencyProperty to work with components) changes are simple to set via Stryle.Triggers and Setters.
<Style x:Key="imbXmlTreeView_itemstyle" TargetType="TreeViewItem">
<Setter Property="Margin" Value="-23,0,0,0" />
<Setter Property="Padding" Value="1" />
<Setter Property="Panel.Margin" Value="0"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{DynamicResource fade_lightGray}" />
<Setter Property="Foreground" Value="{DynamicResource fade_darkGray}" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter Property="Background" Value="{DynamicResource fade_lightGray}" />
<Setter Property="Foreground" Value="{DynamicResource fade_darkGray}" />
</Trigger>
</Style.Triggers>
</Style>
Consider above (just copied from my old project): DynamicResource can be your DataTemplate.
Here is more accurate example you might use:
<Style x:Key="executionFlowBorder" TargetType="ContentControl" >
<Setter Property="Margin" Value="5" />
<Setter Property="ContentTemplate" >
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Border Style="{DynamicResource executionBorder}" DataContext="{Binding}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding Path=isExecuting}" Content="" Grid.Column="0" VerticalAlignment="Center"/>
<Label Content="{Binding Path=displayName, Mode=OneWay}" FontSize="10" Grid.Column="1" FontStretch="Expanded" FontWeight="Black"/>
<Image Source="{Binding Path=iconSource, Mode=OneWay}" Width="16" Height="16" Grid.Column="2" HorizontalAlignment="Right" Margin="0,0,5,0"/>
</Grid>
</Border>
<Label Content="{Binding Path=displayComment, Mode=OneWay}" FontSize="9" HorizontalAlignment="Left"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Where the value of setter can be DynamicResource or one delivered via your MarkupExtension - some thing like I had here:
using System;
using System.Windows;
using System.Windows.Markup;
#endregion
/// <summary>
/// Pristup glavnom registru resursa
/// </summary>
[MarkupExtensionReturnType(typeof (ResourceDictionary))]
public class masterResourceExtension : MarkupExtension
{
public masterResourceExtension()
{
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
try
{
return imbXamlResourceManager.current.masterResourceDictionary;
}
catch
{
return null;
}
}
}
The MarkupExtensions you are using as in example below:
In the XAML code:
<Image Grid.Row="1" Name="image_splash" Source="{imb:imbImageSource ImageName=splash}" Stretch="Fill" />
Added later: just don't forget to add namespace/assembly reference (pointing to the code with the custom MarkupExtension) at top of the XAML Window/Control (in this example it is imbCore.xaml from separate library project of the same solution):
<Window x:Class="imbAPI.imbDialogs.imbSplash"
xmlns:imb="clr-namespace:imbCore.xaml;assembly=imbCore"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Path=splashTitle}" Height="666" Width="896" ResizeMode="NoResize" WindowStyle="ToolWindow" Topmost="False" WindowStartupLocation="CenterScreen"
xmlns:imbControls="clr-namespace:imbAPI.imbControls">
<Grid>
Also have in mind you have to compile it first in order to get it working in XAML designer.
The C# code of the extension used:
using System;
using System.Windows.Markup;
using System.Windows.Media;
using imbCore.resources;
#endregion
[MarkupExtensionReturnType(typeof (ImageSource))]
public class imbImageSourceExtension : MarkupExtension
{
public imbImageSourceExtension()
{
}
public imbImageSourceExtension(String imageName)
{
this.ImageName = imageName;
}
[ConstructorArgument("imageName")]
public String ImageName { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
try
{
if (imbCoreApplicationSettings.doDisableIconWorks) return null;
return imbIconWorks.getIconSource(ImageName);
}
catch
{
return null;
}
}
}
Hope I got your question right on the first place :).
Now I have to sleap :). Good luck!
Added later: ok, I missed your point :) sorry. However, I would leave the response in case you find something useful in the codes I've posted. Bye!
I have XAML related question I have tried to research an answer in vain. I have commented the relevant questions to the XAML. It looks to me this questions is a more complex because of the way I try to arrange things.
Basically I have a main view model used in the TabControl headers and then in the content area I would show a list of items from the main view model. I just don't know how to to the binding. This is the main question. However, I suspect the next and ultimate objectives I have might factor in how to think about this, so I added them too. The rest of the code is for the sake of completeness.
<StackPanel>
<TabControl x:Name="mainsTabControl"
IsSynchronizedWithCurrentItem="True"
IsEnabled="True"
Visibility="Visible"
ItemsSource="{Binding Path=Mains}">
<!-- How to select a different background for the selected header? Note that the background color is "selected tab" if MainContentViewModel.IsActive is not TRUE.
If it is, a different color is chosen. Here this fact is just emulated with IsEnabled property due to well, multi-binding to the rescue (and a converter)? -->
<!-- Is there a clean way to use ICommand binding (RelayCommand) to check if it is OK to change the tab and if necessary, present a dialogue asking for the change? -->
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="{Binding IsActive}"/>
</Style>
</TabControl.ItemContainerStyle>
<!-- This binds to every item in the MainViewModel.Mains collection. Note the question about background color. -->
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" HorizontalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<!-- This binding gives reference to the selected MainContentViewModel. -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="10" DataContext="{Binding ElementName=mainsTabControl, Path=SelectedItem, Mode=OneWay}">
<ItemsControl ItemsSource="{Binding Path=CustomItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
using GalaSoft.MvvmLight;
using System.Collections.ObjectModel;
using System.Linq;
namespace WpfDependencyInjection.ViewModel
{
public class MainContentViewModel: ViewModelBase
{
private ObservableCollection<CustomItemViewModel> customItems;
private mainContentDto MainContent { get; set; }
public string Name { get; }
public bool isActive;
public MainContentViewModel(Engine engine, mainContentDto mainContent)
{
MainContent = mainContent;
Name = MainContent.Name;
IsActive = true;
//The custom items belonging to this main content.
var customItems = engine.CustomItemContents.Where(i => i.MainContentId == MainContent.Id).Select(i => new CustomItemViewModel(engine, i));
CustomItems = new ObservableCollection<CustomItemViewModel>(customItems);
}
public ObservableCollection<CustomItemViewModel> CustomItems
{
get
{
return customItems;
}
set
{
customItems = value;
RaisePropertyChanged(nameof(CustomItems));
}
}
public bool IsActive
{
get
{
return isActive;
}
private set
{
isActive = value;
RaisePropertyChanged(nameof(IsActive));
}
}
}
}
public class CustomItemViewModel: ViewModelBase
{
private Engine Engine { get; }
private ItemTypeDto CustomItem { get; set; }
public string Name { get; }
public CustomItemViewModel(Engine engine, ItemTypeDto customItem)
{
Engine = engine;
CustomItem = customItem;
Name = customItem.Name;
}
}
namespace WpfDependencyInjection
{
public class Engine
{
public string Name { get; } = "EngineMan";
public List<mainContentDto> MainContents { get; set; } = new List<mainContentDto>(new[]
{
new mainContentDto { Name = "Main One", Id = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Version = 1 },
new mainContentDto { Name = "Main Two", Id = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E2"), Version = 1 }
});
public List<ItemTypeDto> CustomItemContents { get; set; } = new List<ItemTypeDto>(new ItemTypeDto[]
{
new ItemType1Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Name = "ItemType1Dto I", Id = Guid.NewGuid(), Version = 1 },
new ItemType2Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Name = "ItemType2Dto I", Id = Guid.NewGuid(), Version = 1 },
new ItemType2Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E2"), Name = "ItemType2Dto 2", Id = Guid.NewGuid(), Version = 1 }
});
public Engine()
{
}
}
}
<edit: The binding solved partially, though not the ICommand part.
Try this:
<TabControl x:Name="mainsTabControl"
IsEnabled="True"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Mains}"
SelectedItem="0"
Visibility="Visible">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter ContentSource="Header" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="HotPink" />
</Trigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:MainContentViewModel}">
<Button Background="{x:Null}"
Command="{Binding SomeCommand}"
Content="{Binding Name}"
FocusVisualStyle="{x:Null}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:MainContentViewModel}">
<ItemsControl Margin="10"
VerticalAlignment="Top"
ItemsSource="{Binding Path=CustomItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:CustomItem}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Command:
There may be cleaner ways but here we bind IsSelected for the TabItem to the IsSelected property of the viewmodel. This enables having a command that asks if it is ok to navigate and sets IsSelected to true if it is.
Background:
We also retemplate the tabitem so that background works as we want. If you check with Snoop WPF inserts an extra border when the item is selected.
Side note 1:
Don't put the TabControl into a StackPanel like that. A StackPanel sizes to content and will kill scrolling and draw outside the control. Also it comes with a cost, a deep visual tree is not cheap. Same in the ItemTemplate and the other places. In fact StackPanel is rarely right for anything :)
Side note 2:
If you specify DataType in your DataTemplate you get intellisense and some compiletime checking.