I'm new to Xamarin (and new in coding in general).
I'm using xct TouchEffect within a ListView to try to get a LongPress menu.
Since the TouchEffect.LongPressCommand is a Command, I can only bound it to the model page from some reason.
So... I'm trying to send information to the Code-behind via MessagingCenter.
The problem I have is the message is not receiving.
I read a lot and tried to figure it out, and I guess to be able to receive the message, the subscriber needs to be instantiate/initialize first.
The main problem I have is... I don't know how to do it lol.
Or the whole thing I'm trying to do is wrong?
I will add some code but if anything else is needed please let me know.
Note: the loading page (when the app start) is a GroupPage(which working fine), the problem is with the ItemsPage.
Thank you so much for everyone.
ItemsPage.xaml
<ContentPage.BindingContext>
<localvm:ItemViewModel/>
</ContentPage.BindingContext>
<ListView ItemsSource="{Binding Items, Mode=TwoWay}" x:Name="lstView"
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
SelectedItem="{Binding SelectedItem}" HasUnevenRows="True" RowHeight="50">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="0,0,8,0" Margin="4,0,4,0" xct:TouchEffect.LongPressCommand="{Binding LongPressItemCommand}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="7*"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding ItemName}" TextColor="Black" Grid.Column="1" FontSize="Medium"></Label>
<Label Text="{Binding ItemDescription}" Grid.Column="1" VerticalTextAlignment="End"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ItemsPage.cs
namespace MobileApp2
{
public partial class ItemsPage : ContentPage
{
public ItemsPage()
{
InitializeComponent();
MessagingCenter.Subscribe<Item, Guid>(this, "PopupMenuItemMsg",
(page, itemId) =>
{
Main_PopupMenu(itemId);
});
}
public async void Main_PopupMenu(Guid itemId)
{
DisplayActionSheet("Test", "Test", "OK");
}
}
}
Items.cs (model)
namespace MobileApp2
{
public class Item : INotifyPropertyChanged
{
public Command LongPressItemCommand { get; }
public Guid ItemId { get; set; }
public Guid GroupId { get; set; }
private string itemName = string.Empty;
public string ItemName
{
get { return itemName; }
set
{
if (value != null) itemName = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ItemName"));
}
}
private string itemDescription = string.Empty;
public string ItemDescription
{
get
{
return itemDescription.Trim();
}
set
{
if (value != null) itemDescription = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ItemDescription"));
}
}
public Item(string itemName, string itemDescription)
{
ItemName = itemName;
ItemDescription = itemDescription;
}
public Item()
{
LongPressItemCommand = new Command(() =>
{
MessagingCenter.Send<Item, Guid>(this, "PopupMenuItemMsg", ItemId);
});
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
You could check the code below with relative binding of Command.
Model:
public class Item
{
public string ItemName { get; set; }
public string ItemDescription { get; set; }
}
ViewModel:
public class ItemViewModel
{
public ICommand LongPressItemCommand { get; set; }
public Guid ItemId { get; set; }
public ObservableCollection<Item> Items { get; set; }
public ItemViewModel()
{
LongPressItemCommand = new Command(() =>
{
MessagingCenter.Send<ItemViewModel, Guid>(this, "PopupMenuItemMsg", ItemId);
});
CreateCollection();
}
public void LongPress()
{
}
public void CreateCollection()
{
Items = new ObservableCollection<Item>()
{
new Item(){ ItemName="A", ItemDescription="AA"},
new Item(){ ItemName="B", ItemDescription="BB"},
new Item(){ ItemName="C", ItemDescription="CC"},
};
}
}
Xaml:
<ContentPage.BindingContext>
<localvm:ItemViewModel></localvm:ItemViewModel>
</ContentPage.BindingContext>
<StackLayout>
<ListView ItemsSource="{Binding Items, Mode=TwoWay}" x:Name="lstView"
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
SelectedItem="{Binding SelectedItem}" HasUnevenRows="True" RowHeight="50">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="0,0,8,0" Margin="4,0,4,0" xct:TouchEffect.LongPressCommand="{Binding Path=BindingContext.LongPressItemCommand, Source={x:Reference Name=lstView}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding ItemName}" TextColor="Black" Grid.Column="0" FontSize="Medium"></Label>
<Label Text="{Binding ItemDescription}" Grid.Column="1" VerticalTextAlignment="End"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Code behind:
MessagingCenter.Subscribe<ItemViewModel, Guid>(this, "PopupMenuItemMsg",
(page, itemId) =>
{
Main_PopupMenu(itemId);
});
Related
I have a design with vertical and horizontal scrolling.I need to add two lists in to the same page.So I have used bindable layout for this but the scrolling is not working on this. Please help me on the below. I am stuck on this for a week.
Here is my View and ViewModel
<StackLayout BackgroundColor="WhiteSmoke">
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,*,Auto">
<StackLayout
Grid.Row="0"
Grid.Column="1"
BindableLayout.ItemsSource="{Binding ListSource.TestList2}"
Orientation="Horizontal"
VerticalOptions="FillAndExpand">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label
Margin="0" WidthRequest="75"
FontSize="16"
HeightRequest="58"
HorizontalOptions="FillAndExpand"
MaxLines="3"
Text="{Binding Subname}"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
<StackLayout x:Name="layout1"
Grid.Row="1" Margin="30,0,0,0"
Grid.Column="0" Grid.ColumnSpan="2"
BindableLayout.ItemsSource="{Binding ListSource.TestList}"
Orientation="Vertical">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Grid ColumnDefinitions="*,*">
<Label
Grid.Column="0"
Margin="3"
Text="{Binding name}" />
<StackLayout
Grid.Column="1"
Margin="0"
BindableLayout.ItemsSource="{Binding classes}"
HorizontalOptions="FillAndExpand"
Orientation="Horizontal">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
<RadioButton WidthRequest="75"
HorizontalOptions="FillAndExpand"
IsChecked="{Binding IsFailed}"/>
<Entry Placeholder="Subject"
IsVisible="{Binding IsEntry}"/>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</Grid>
</StackLayout>
ViewModel
public class ListViewVM:BaseViewModel
{
private TestClass _listSource;
public TestClass ListSource
{
get { return _listSource; }
set { _listSource = value;
OnPropertyChanged("ListSource");
}
}
//public
public ListViewVM()
{
TestClass kk=new TestClass();
kk.BindData();
ListSource = new TestClass();
ListSource.TestList =new List<TestName>(kk.TestList);
ListSource.TestList2= new List<TestSubject>( kk.TestList2);
ListSource.TestList.ForEach(x => x.classes=kk.TestList2);
var item = ListSource;
}}
Model
public class TestClass
{
public List<TestName> TestList { get; set; }
public List<TestSubject> TestList2 { get; set; }
public void BindData()
{
TestList = new List<TestName>() { new TestName {id=1,name="Jo"},
new TestName {id=2,name="Annie" },
new TestName {id=3,name="Alex" }};
TestList2 = new List<TestSubject>() { new TestSubject { id=1,Subname="Maths"},
new TestSubject { id=2,Subname="Physics"},
new TestSubject { id=2,Subname="Chemestry"},
new TestSubject { id=2,Subname="Other",IsEntry=true}};
}
}
public class TestName
{
public int id { get; set; }
public string name { get; set; }
public List<TestSubject> classes { get; set; }
}
public class TestSubject
{
public int id { get; set; }
public string Subname { get; set; }
public bool IsFailed {get;set;}
public bool IsOthersSelected { get; set; }
public bool IsEntry { get; set; }
}
Current UI
Some conditions is there , the user should select only one option from each row, but in my design they can select multiple option. If I add the RadioButton without that stacklayout the selection case is working but that time I cannot add the Entry field.If the user select "other" , then I need display an input field at the end along with "other" option for adding the other subject . So that field also mandatory for me.
I have a list of tasks that I need to bind a Boolean variable to its item.
the task object includes a completedDate property that if it has value defines the task as completed.
on the view, I need to check if it has value the button text display the text: "mark as incomplete"
----Task Object-----
public class ProjectTaskLineItemSummary
{
public int TenantId { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal? CostMultiplier { get; set; }
public DateTimeOffset? CompletedDate { get; set; }
public int? CompletedByUserId { get; set; }
}
-------viewmodel-------
viewmodel()
{
public ObservableCollection<ProjectTaskLineItemSummary> Tasks { get; set; }
...
bool isCompleted;
public bool IsCompleted
{
get {
return isCompleted;
}
set
{
isCompleted = value;
OnPropertyChanged();
}
}
}
-----view----
<CollectionView Grid.Row="1" ItemsSource="{Binding Tasks}" x:Name="List3">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid>
<Frame
Margin="0,10"
Padding="10"
BackgroundColor="{StaticResource PrimaryWhite}"
BorderColor="{StaticResource PrimaryLightGray}"
CornerRadius="10"
HasShadow="False">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto" RowSpacing="15">
<StackLayout HorizontalOptions="EndAndExpand" Orientation="Horizontal">
<Image
HeightRequest="20"
Source="iconCalender.png"
WidthRequest="20" />
<Label
FontFamily="{StaticResource MeduimFont}"
Style="{StaticResource LabelMedium}"
Text="{Binding CompletedDate,StringFormat='{0:MMMM dd, yyyy}'}"
TextColor="{StaticResource PrimaryBlack}"
/>
</StackLayout>
</StackLayout>
<BoxView
Grid.Row="1"
HeightRequest="1"
Color="{StaticResource PrimaryLightGray}" />
<Label
Grid.Row="2"
Style="{StaticResource LabelMedium}"
Text="{Binding Name}"
TextColor="{StaticResource PrimaryBlack}" />
<Button
x:Name="lstbtnMarkasComplite"
Grid.Row="5"
Padding="15,0"
Clicked="MarkTaskAsCompletedClicked"
CornerRadius="20"
FontSize="{StaticResource Font12}"
HeightRequest="40"
CommandParameter="{Binding Id}"
HorizontalOptions="CenterAndExpand"
Style="{StaticResource ButtonPurple}"
Text="Mark as Completed" >
<Button.Triggers>
<DataTrigger TargetType="Button" Binding="{Binding IsCompleted}" Value="True">
<Setter Property="Text" Value="Mark Task as In Completed"/>
</DataTrigger>
</Button.Triggers>
</Button>
</Grid>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
I tried to assign it in view model like below but it doesn't work:
public override async Task InitializeAsync(object navigationData)
{
await SetBusyAsync(async () =>
{
...
Tasks = ObjectMapper.Map<ObservableCollection<ProjectTaskLineItemSummary>>(project.TaskLineItems);
foreach (var task in Tasks)
{
isCompleted = task.CompletedDate.HasValue ? true : false;
}
RaisePropertyChanged(() => Model);
RaisePropertyChanged(() => Notes);
RaisePropertyChanged(() => Files);
RaisePropertyChanged(() => Tasks);
});
}
This could simply be achieved by Binding Text Property of the button and then dynamically set the Text of the button based on the CompletedDate of the entity.
Below is the code snippets for your reference:
Model:
ProjectTaskLineItemSummary.cs
public class ProjectTaskLineItemSummary
{
public int TenantId { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal? CostMultiplier { get; set; }
public DateTimeOffset? CompletedDate { get; set; }
public int? CompletedByUserId { get; set; }
public string CompletedButton { get; set; }
}
View:
<CollectionView ItemsSource="{Binding Tasks}" x:Name="List3" Background="aqua">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid>
<Frame Margin="0,10" Padding="10" HasShadow="False">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto" RowSpacing="15">
<StackLayout HorizontalOptions="EndAndExpand" Orientation="Horizontal">
<Image HeightRequest="30" Source="XamarinLogo.png" WidthRequest="80" />
<Label x:Name="mydate" Text="{Binding CompletedDate,StringFormat='{0:MMMM dd, yyyy}'}" TextColor="Black"/>
</StackLayout>
<BoxView Grid.Row="1" HeightRequest="1" Color="Black" />
<Label Grid.Row="2" Text="{Binding Name}" TextColor="Black" />
<Button Grid.Row="5" x:Name="lstbtnMarkasComplite" Padding="15,0"
Clicked="MarkTaskAsCompletedClicked"
CornerRadius="20"
Text="{Binding CompletedButton}"
FontSize="Medium"
HeightRequest="40"
CommandParameter="{Binding Id}"
HorizontalOptions="CenterAndExpand">
</Button>
</Grid>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
ViewModel:
public class PageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ProjectTaskLineItemSummary> Tasks { get; set; }
bool isCompleted { get; set; }
public bool IsCompleted
{
get => isCompleted;
set
{
isCompleted = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(IsCompleted)));
}
}
string completed { get; set; }
public String CompletedButton
{
get => completed;
set
{
completed = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(CompletedButton)));
}
}
public PageViewModel()
{
Tasks = new ObservableCollection<ProjectTaskLineItemSummary>()
{
new ProjectTaskLineItemSummary(){TenantId = 1, Id =1, Name = "jobs ", Description= "jjjj",CostMultiplier=1 ,CompletedDate =DateTime.UtcNow, CompletedByUserId=1 ,CompletedButton = ""},
new ProjectTaskLineItemSummary(){TenantId = 2, Id =2, Name = "james ",Description= "aaaa",CostMultiplier=2 , CompletedByUserId=2,CompletedButton = "" },
new ProjectTaskLineItemSummary(){TenantId = 3, Id =3, Name = "rollex ",Description= "rrrr",CostMultiplier=3 ,CompletedDate =DateTime.UtcNow, CompletedByUserId=3 ,CompletedButton = ""}
};
setButtonIsCompleted();
}
private void setButtonIsCompleted()
{
foreach (var task in Tasks)
{
if (task.CompletedDate == null)
{
task.CompletedButton = "Mark Task as Completed";
}
else
{
task.CompletedButton = "Mark Task as inCompleted";
}
}
}
}
I have 3 classes which are named Users, Cards, and BindingUser which is a class to bind them together.
public class User
{
public int Uid { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class CardData
{
public int _id { get; set; }
public string CardName { get; set; }
public string CardNote { get; set; }
}
public class BindingUser
{
public User bUser { get; set; }
public ObservableCollection<CardData> cardDatas { get; set; }
public BindingUser()
{
cardDatas = new ObservableCollection<CardData>();
}
}
I am trying to create a Horizontal stack layout, filled with frames for each user and in each frame is a list of cards belonging to that user.
I have tried doing this with listviews, stacklayouts and pretty much every other method google shows but each has the same result.
I get the frames for users, but they aren't populated.
My xaml looks like this, I know this is wrong but I formatted it like this to show what I am trying to achieve. In this example, the FirstName and LastName are working fine but the listviews aren't being populated.
<ContentPage.Content>
<StackLayout Orientation="Horizontal" BackgroundColor="#EBEBEB" HeightRequest="130" BindableLayout.ItemsSource="{Binding bindingUsers}" WidthRequest="410">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout HeightRequest="300" VerticalOptions="Start" Orientation="Vertical">
<Frame CornerRadius="0"
HorizontalOptions="Start"
VerticalOptions="Start"
Margin="0"
Padding="0"
WidthRequest="410"
HeightRequest="80"
BackgroundColor="Red"
HasShadow="true">
<StackLayout Orientation="Vertical">
<Label Text="{Binding bUser.FirstName}"/>
<Label Text="{Binding bUser.LastName}"/>
</StackLayout>
</Frame>
<StackLayout Orientation="Vertical" BackgroundColor="Blue">
<ListView BindableLayout.ItemsSource="{Binding bindingUsers.cardDatas}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text ="{Binding CardName}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ContentPage.Content>
Can someone point me in the right direction for what I am trying to achieve?
In .net forms it is such a simple task, and would take me minutes to do, but this has me beaten.
Here you have considered two things,
The first one, for listview, you have to use ItemsSource property instead of BindableLayout.ItemsSource.
The second one is the BindingContext of the template will be a single item of the bindable layout source. For example, in your case CardData. So the source, your trying to binding to the ListView is not a part of CardData. So when you bind different objects inside the data template, you have bound with BindingContext keyword like BindingContext.YourProperty with source reference. Refer to the below code,
<StackLayout x:Name="baseLayout" Orientation="Horizontal" BackgroundColor="#EBEBEB" HeightRequest="130"
BindableLayout.ItemsSource="{Binding cardDatas}" WidthRequest="410">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout HeightRequest="300" VerticalOptions="Start" Orientation="Vertical">
<Frame CornerRadius="0"
HorizontalOptions="Start"
VerticalOptions="Start"
Margin="0"
Padding="0"
WidthRequest="410"
HeightRequest="80"
BackgroundColor="Red"
HasShadow="true">
<StackLayout Orientation="Vertical">
<Label Text="{Binding BindingContext.bUser.FirstName, Source={x:Reference baseLayout}}"/>
<Label Text="{Binding BindingContext.bUser.LastName, Source={x:Reference baseLayout}}"/>
</StackLayout>
</Frame>
<StackLayout Orientation="Vertical" BackgroundColor="Blue">
<ListView ItemsSource="{Binding BindingContext.cardDatas, Source={x:Reference baseLayout}}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text ="{Binding CardName}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
And the model and view model class based on your codes,
public class User
{
public int Uid { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class CardData
{
public int _id { get; set; }
public string CardName { get; set; }
public string CardNote { get; set; }
}
public class BindingUser
{
public User bUser { get; set; }
public ObservableCollection<CardData> cardDatas { get; set; }
public BindingUser()
{
cardDatas = new ObservableCollection<CardData>()
{
new CardData()
{
_id = 1,
CardName = "Testing1",
CardNote = "Test data1",
},
new CardData()
{
_id = 2,
CardName = "Testing2",
CardNote = "Test data2",
},
new CardData()
{
_id = 3,
CardName = "Testing3",
CardNote = "Test data3",
},
};
bUser = new User
{
Uid = 23432,
FirstName = "First User",
LastName = "Last User",
};
}
}
It's better use the MVVM model if you're binding complex data, the view model helps you get a clearer picture of the view and model, I made some change to the code and hope it helps:
XAML:
<StackLayout BindableLayout.ItemsSource="{Binding Data}"
Orientation="Horizontal" BackgroundColor="#EBEBEB">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame CornerRadius="0"
HorizontalOptions="Start"
VerticalOptions="Start"
Margin="0"
Padding="0"
WidthRequest="410"
BackgroundColor="Red"
HasShadow="true">
<StackLayout Orientation="Vertical">
<Label Text="{Binding FirstName}"/>
<Label Text="{Binding LastName}"/>
</StackLayout>
</Frame>
<ListView ItemsSource="{Binding cardDatas}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text ="{Binding CardName}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
set BindingContext in MainPage:
public partial class MainPage : ContentPage
{
DemoViewModel viewModel = new DemoViewModel();
public MainPage()
{
InitializeComponent();
this.BindingContext = viewModel;
}
}
DemoViewModel:
class DemoViewModel
{
public ObservableCollection<BindingUser> Data { get; set; }
public DemoViewModel() {
Data = new ObservableCollection<BindingUser>
{
new BindingUser(1, "aa"),
new BindingUser(2, "bb"),
new BindingUser(3, "cc"),
new BindingUser(4, "dd")
};
}
}
Class User is deleted, CardData remains the same, also you can add it back if needed.
BindingUser:
class BindingUser
{
public int Uid { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ObservableCollection<CardData> cardDatas { get; set; }
public BindingUser(int uid, string name)
{
Uid = uid;
FirstName = name;
LastName = name;
cardDatas = new ObservableCollection<CardData>()
{
new CardData()
{
_id = 1,
CardName = "card 1",
},
new CardData()
{
_id = 2,
CardName = "card 2",
},
new CardData()
{
_id = 3,
CardName = "card 3",
},
};
}
}
I have a ListView with a ItemSource binding to a list object; inside the listview there are some items that are filled with the values of the list object. I have 2 checkboxes that are binding to elements of the list object and want to uncheck one when the other is checked. In the code I wrote, the values are correctly changed in the list object but the checkbox didn't change (stay unchecked). Next is the code that I wrote.
XAML Part
<ListView ItemsSource="{Binding ListaAsistencia}" HasUnevenRows="True" SelectionMode="None" x:Name="AsistList">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame Margin="5,2,5,2" Padding="0" BackgroundColor="Transparent" BorderColor="#915c0d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".7*"></ColumnDefinition>
<ColumnDefinition Width=".3*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Label Text="{Binding alumno, Mode=TwoWay}" Grid.Row="0" Grid.Column="0" FontSize="Micro" Margin="5,2,2,2"/>
<Entry Placeholder="Notas" Text="{Binding notas}" Grid.Row="1" Grid.Column="0" FontSize="Micro" TextColor="Black" />
<Grid Grid.Row="0" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".65*"></ColumnDefinition>
<ColumnDefinition Width=".35*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Text="AsistiĆ³" Grid.Column="0" HorizontalOptions="End" FontSize="Micro" VerticalOptions="Center"/>
<CheckBox IsChecked="{Binding asistencia, Mode=TwoWay}" Grid.Column="1" HorizontalOptions="Start" Color="Black"/>
</Grid>
<Grid Grid.Row="1" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".65*"></ColumnDefinition>
<ColumnDefinition Width=".35*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Text="F. Just." Grid.Column="0" FontSize="Micro" HorizontalOptions="End" VerticalOptions="Center"/>
<CheckBox IsChecked="{Binding falta_justificada, Mode=TwoWay}" Grid.Column="1" HorizontalOptions="Start" Color="DarkBlue" CheckedChanged="CheckBox_Just_CheckedChanged" AutomationId="{Binding idalumno_grupo}"/>
</Grid>
</Grid>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
C# BackEnd Part
private void CheckBox_Just_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
var vm = BindingContext as AsisCapturarViewModel;
if ((e.Value) && (!vm.obtainingData))
{
CheckBox switchBox = (CheckBox)sender;
vm.UncheckFalta(switchBox.AutomationId);
}
}
C# View Model Part
public async void UncheckFalta(string idalumno_grupo)
{
if (!String.IsNullOrEmpty(idalumno_grupo))
{
int idalumno_grupoUse = Convert.ToInt32(idalumno_grupo);
ListaAsistencia.Where(a => a.idalumno_grupo == idalumno_grupoUse).ToList().ForEach(s => s.asistencia = false);
}
}
Class used in the List
public class AsistenciaList
{
public int idasistencia { get; set; }
public DateTime fecha { get; set; }
public int idtipo_evento { get; set; }
public string tipo_evento { get; set; }
public int idmaestro_grupo { get; set; }
public int idalumno_grupo { get; set; }
public string alumno { get; set; }
public bool asistencia { get; set; }
public string notas { get; set; }
public bool falta_justificada { get; set; }
}
Thanks for your help.
Firstly , agree with Jason .You should implement the interface INotifyPropertyChanged in your model if you want to update UI in runtime .
In addition , since you have used MVVM , you should put all the logic to your viewmodel .
So you can improve your code as following
in your model
public class AsistenciaList:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public int idasistencia { get; set; }
public DateTime fecha { get; set; }
public int idtipo_evento { get; set; }
public string tipo_evento { get; set; }
public int idmaestro_grupo { get; set; }
public int idalumno_grupo { get; set; }
public string alumno { get; set; }
public string notas { get; set; }
private bool asis;
public bool asistencia
{
get
{
return asis;
}
set
{
if (asis != value)
{
asis = value;
NotifyPropertyChanged();
}
}
}
private bool falta;
public bool falta_justificada
{
get
{
return falta;
}
set
{
if (falta != value)
{
falta = value;
NotifyPropertyChanged();
}
}
}
}
xaml
<CheckBox IsChecked="{Binding falta_justificada, Mode=TwoWay}" Grid.Column="1" HorizontalOptions="Start" Color="DarkBlue" AutomationId="{Binding idalumno_grupo}"/>
ViewModel
//...
foreach(AsistenciaList asistencia in ListaAsistencia)
{
asistencia.PropertyChanged += Asistencia_PropertyChanged;
}
//...
private void Asistencia_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName== "falta_justificada")
{
AsistenciaList asistencia = sender as AsistenciaList;
var idalumno_grupo = asistencia.idalumno_grupo;
//...do something you want
}
}
I have a ListBox which is bound to a List of DataModel.
DataModel.cs
public class DataModel
{
public string Name { get; set; }
public string Desc { get; set; }
public string Code { get; set; }
}
The ListBox should display two properties, so I have defined the ItemTemplate as below
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text=" - "></TextBlock>
<TextBlock Text="{Binding Code}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
My requirement is that the user can select which two properties to display in the ListBox at run time. I am not sure how to achieve this. I have created a sample solution to explain my problem.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="5">
<Label Content="Property One"></Label>
<ComboBox Name="ComboBox1" Margin="3" Width="150" ItemsSource="{Binding DataModelProperties}"></ComboBox>
<Label Content="Property Two"></Label>
<ComboBox Name="ComboBox2" Margin="3" Width="150" ItemsSource="{Binding DataModelProperties}"></ComboBox>
<Button Content="Go" Click="ButtonBase_OnClick" Margin="3"></Button>
</StackPanel>
<ListBox Grid.Row="1" ItemsSource="{Binding DataModelList}" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text=" - "></TextBlock>
<TextBlock Text="{Binding Code}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
}
}
public class ViewModel
{
public List<String> DataModelProperties { get; set; }
public List<DataModel> DataModelList { get; set; }
public ViewModel()
{
DataModelList = new List<DataModel>();
DataModelList.Add(new DataModel() { Name = "Name1", Code = "Code1", Desc = "Desc1" });
DataModelList.Add(new DataModel() { Name = "Name2", Code = "Code2", Desc = "Desc2" });
DataModelList.Add(new DataModel() { Name = "Name3", Code = "Code3", Desc = "Desc3" });
DataModelProperties = typeof(DataModel).GetProperties().Select(s => s.Name).ToList();
}
}
public class DataModel
{
public string Name { get; set; }
public string Desc { get; set; }
public string Code { get; set; }
}
}
Things I have tried
<TextBlock Text="{Binding ElementName=ComboBox1, Path=SelectedItem}"></TextBlock>
This just displays the selected property name.
I suggest you to manage all the stuff regarding the "dynamic description" showed in the ListBox in your ViewModel.
First of all, your view model and your model should implement INotifyPropertyChanged. In my sample I created a simple base class which implements it. My base class is called NotifyPropertyChangedImpl.
Moreover I added two properties to the view model: the comboboxes for selecting the properties are binded to those two properties.
public class ViewModel : NotifyPropertyChangedImpl
{
private string property1;
private string property2;
public List<String> DataModelProperties { get; set; }
public List<DataModel> DataModelList { get; set; }
public string Property1
{
get
{
return property1;
}
set
{
if (property1 != value)
{
property1 = value;
SetDynamicDescriptions();
}
}
}
public string Property2
{
get
{
return property2;
}
set
{
if (property2 != value)
{
property2 = value;
SetDynamicDescriptions();
}
}
}
public ViewModel()
{
DataModelList = new List<DataModel>();
DataModelList.Add(new DataModel() { Name = "Name1", Code = "Code1", Desc = "Desc1" });
DataModelList.Add(new DataModel() { Name = "Name2", Code = "Code2", Desc = "Desc2" });
DataModelList.Add(new DataModel() { Name = "Name3", Code = "Code3", Desc = "Desc3" });
DataModelProperties = typeof(DataModel).GetProperties().Select(s => s.Name).ToList();
}
private void SetDynamicDescriptions()
{
PropertyInfo propertyInfo1;
PropertyInfo propertyInfo2;
Type type = typeof(DataModel);
if (!String.IsNullOrEmpty(property1) && !String.IsNullOrEmpty(property2))
{
propertyInfo1 = type.GetProperty(property1);
propertyInfo2 = type.GetProperty(property2);
foreach (DataModel dataModel in DataModelList)
{
dataModel.DynamicDescription = String.Format("{0} - {1}",
propertyInfo1.GetValue(dataModel, null), propertyInfo2.GetValue(dataModel, null));
}
}
}
}
As you can see the method SetDynamicDescriptions rebuilds the DynamicsDescription each time Property1 or Property2 changes.
I also added a property to the model class:
public class DataModel : NotifyPropertyChangedImpl
{
private string dynamicDescription;
public string Name { get; set; }
public string Desc { get; set; }
public string Code { get; set; }
public string DynamicDescription
{
get
{
return dynamicDescription;
}
set
{
if (dynamicDescription != value)
{
dynamicDescription = value;
OnPropertyChanged("DynamicDescription");
}
}
}
}
So at the end your XAML will be:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="5">
<Label Content="Property One"></Label>
<ComboBox Name="ComboBox1" Margin="3" Width="150" ItemsSource="{Binding DataModelProperties}"
SelectedItem="{Binding Property1}"></ComboBox>
<Label Content="Property Two"></Label>
<ComboBox Name="ComboBox2" Margin="3" Width="150" ItemsSource="{Binding DataModelProperties}"
SelectedItem="{Binding Property2}"></ComboBox>
</StackPanel>
<ListBox Grid.Row="1" ItemsSource="{Binding DataModelList}" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DynamicDescription}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I hope this can help you.
In short, what I've done is I made 2 public properties to bind to and changed your properties to fields. Also I've made custom attribute, to control what fields user is able to select. And created a DisplayOptions class to store selection and spread it across DataModel instances.
The solution is a bit messy, considering it's a PoC, but I believe this can do:
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="5">
<Label Content="Property One"></Label>
<ComboBox Name="ComboBox1" Margin="3" Width="150" ItemsSource="{Binding DataModelProperties}"></ComboBox>
<Label Content="Property Two"></Label>
<ComboBox Name="ComboBox2" Margin="3" Width="150" ItemsSource="{Binding DataModelProperties}"></ComboBox>
<Button Content="Go" Click="ButtonBase_OnClick" Margin="3"></Button>
</StackPanel>
<ListBox Grid.Row="1" ItemsSource="{Binding DataModelList}" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DisplayProperty1}"></TextBlock>
<TextBlock Text=" - "></TextBlock>
<TextBlock Text="{Binding DisplayProperty2}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
CS:
public partial class MainWindow : Window
{
public ViewModel model = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = model;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
//This part has to be on the View side, but this is PoC
model.Opts.Prop1 = typeof(DataModel).GetFields()
.Where(a => a.Name == ComboBox1.SelectedItem.ToString()).FirstOrDefault();
model.Opts.Prop2 = typeof(DataModel).GetFields()
.Where(a => a.Name == ComboBox2.SelectedItem.ToString()).FirstOrDefault();
DataContext = null;
DataContext = model;
}
}
public class ViewModel
{
public DisplayOptions Opts = new DisplayOptions();
public List<String> DataModelProperties { get; set; }
public List<DataModel> DataModelList { get; set; }
public ViewModel()
{
var properties = typeof(DataModel).GetFields()
.Where(a => a.CustomAttributes.Any(b => b.AttributeType == typeof(SwitchableAttr)));
//Initialising options before creating DataModel instances
DataModelProperties = properties.Select(s => s.Name).ToList();
Opts.Prop1 = properties.ElementAt(0);
Opts.Prop2 = properties.ElementAt(1);
DataModelList = new List<DataModel>();
DataModelList.Add(new DataModel() { Name = "Name1", Code = "Code1", Desc = "Desc1", options = Opts });
DataModelList.Add(new DataModel() { Name = "Name2", Code = "Code2", Desc = "Desc2", options = Opts });
DataModelList.Add(new DataModel() { Name = "Name3", Code = "Code3", Desc = "Desc3", options = Opts });
}
}
public class DisplayOptions
{
public FieldInfo Prop1;
public FieldInfo Prop2;
}
public class DataModel
{
public DisplayOptions options;
[SwitchableAttr]
public string Name;
[SwitchableAttr]
public string Desc;
[SwitchableAttr]
public string Code;
public string DisplayProperty1 { get { return (string)options.Prop1.GetValue(this); } set { } }
public string DisplayProperty2 { get { return (string)options.Prop2.GetValue(this); } set { } }
}
public class SwitchableAttr : Attribute { }
This is not Complete MVVM
so here is the code
public partial class MainWindow : Window
{
private ViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new ViewModel();
this.DataContext = vm;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(ComboBox1.Text) && !string.IsNullOrEmpty(ComboBox2.Text))
{
vm.AddDataToModel(ComboBox1.Text, ComboBox2.Text);
}
}
}
public class ViewModel
{
public List<String> DataModelProperties { get; set; }
private ObservableCollection<DataModel> _DataModelList;
public ViewModel()
{
_DataModelList = new ObservableCollection<DataModel>();
DataModelProperties = typeof(DataModel).GetProperties().Select(s => s.Name).ToList();
}
public void AddDataToModel(string cmbx1Val,string cmbx2Val)
{
_DataModelList.Add(new DataModel() { Name = cmbx1Val, Code = cmbx2Val, Desc = "Desc1" });
}
public ObservableCollection<DataModel> DataModelList
{
get
{
return _DataModelList;
}
set
{
_DataModelList = value;
}
}
}