I want to create Xamarin search page with using MVVM. I have created a logic in my MainWindowViewModel which has to update my ListView once user entered a character into the searchar. But i have such result: In some reason UI is not updating
I do not know what i done wrong.
And also i would like to invoke ExecuteSearchCommand asynchronysly, and i will approsiate if you show how to implement it correctly.
Thanks.
<ContentPage.BindingContext>
<ViewModels:MainWindowViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<SearchBar SearchCommand="{Binding SearchCommand}"
Text="{Binding EnteredText}"
/>
<Label Text="{Binding EnteredText}"></Label>
<ListView x:Name="lstContatos" ItemsSource="{Binding MyList}" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Grid MinimumHeightRequest="80" HeightRequest="120" HorizontalOptions="FillAndExpand" >
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Text="{Binding PhrasalVerb}"/>
<Button Text="Delete" Grid.Column="1" BackgroundColor="Black" HeightRequest="30" WidthRequest="40" IsVisible="True"/>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
This is my ViewModel that binds to the View
public string EnteredText
{
get { return enteredText; }
set
{
enteredText = value;
this.SearchCommand.Execute(null);
OnPropertyChanged(nameof(EnteredText));
}
}
void ExecuteSearchCommand(object parameter)
{
if (enteredText.Length>=1)
{
MyList = new ObservableCollection<PhV_Get>(phrasalVerbGet
.Where(x => x.PhrasalVerb.ToLower()
.Contains(enteredText.ToLower())).ToList());
}
else
{
MyList = phrasalVerbGet;
}
}
public ObservableCollection<PhV_Get> MyList
{
set
{
phrasalVerbGet = value;
OnPropertyChanged(nameof(MyList));
}
get
{
return phrasalVerbGet;
}
}
public Command SearchCommand {
get
{
return new Command(ExecuteSearchCommand,
CanExecuteSeachCommand);
}
}
public bool CanExecuteSeachCommand(object parameter)
{
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
this.PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(propertyName));
}
The line MyList = new ObservableCollection in ExecuteSearchCommand is a no-no. Your listview is bound to the old ObservableCollection - when you create a new one, then you break that binding and the view will not update because it doesn't know anything about the new one.
When you need to change the contents of an observable collection, do it like this (in pseudocode):
MyList.Clear();
foreach (thing in myListOfThings)
{
MyList.Items.Add(thing);
}
That way you are updating the collection that the ListView is bound to, and the ListView will see the changes.
Related
I am binding ObservableCollection with CollectionView.
<CollectionView ItemsSource="{Binding LeftExercises}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:ExerciseModel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="4*" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Text="{Binding SetNumber}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Black" FontSize="Medium" />
<Label Grid.Column="1" Grid.Row="0" Text="{Binding Weight}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Black" FontSize="Medium" />
<Label Grid.Column="2" Grid.Row="0" Text="{Binding Reps}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Black" FontSize="Medium" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
private ObservableCollection<ExerciseModel> _leftExercises;
public ObservableCollection<ExerciseModel> LeftExercises
{
get => _leftExercises;
set
{
if (_leftExercises != value)
{
_leftExercises = value;
OnPropertyChanged(nameof(LeftExercises));
}
}
}
When I add a new object to the Collection, it will reflect in my UI but whenever I try to update the value of any object, it will not reflect.
This is my model
public class ExerciseModel
{
public int SetNumber { get; set; }
public decimal Weight { get; set; }
public int Reps { get; set; }
public ExerciseType ExerciseType { get; set; }
public Side Side { get; set; }
}
I am incrementing the Reps (update Reps property) from the below command.
private Command _dummyLeftIncreaseRepsCommand;
public Command dummyLeftIncreaseRepsCommand
{
get
{
return _dummyLeftIncreaseRepsCommand ??= new Command(() =>
{
ExerciseModel lastObj = LeftExercises.Last(x => x.Side == SharedVM.ActiveSide);
lastObj.Reps += 1;
});
}
}
Your ExerciseModel class needs to implement INotifyPropertyChanged.
To do so, simply add the interface name to your class code, like this:
class MyClass : INotifyPropertyChanged
Alternatively, you can set the ItemsSource for the object again. Doing so will update the visual as well. You can do this at runtime like this:
XAML file:
<CollectionView x:Name="MyView">[...]
Code in background:
MyView.ItemsSource = MyCollectionVar;
I advise you to use CommunityToolkit.MVVM and CommunityToolkit.MAUI, when you try to bind commands, observable properties and collections to your XAML.
The benefit of this will be that you wont have to write walls of code, and spend hours to figure out why it is not working.
It saves you time, that you can put in something useful, instead of dealing with boilerplate code.
What I'm trying to implement is a message chat bubble wherein you tap the control/message and the message would expand showing details of the date and the seen / sent status below. I do have a DataTemplate Selector for the different controls I have for the Sender and the Receiver.
My problem is changing the Height of the message in the ListView. I tried implementing the Binding of the RowDefinition to whatever the Height variable is in my Message class (the class that holds info regarding the message). Although the height was updated, it didn't reflect on the ListView. I've scoured over the internet for existing chat UI templates but I think most of them are paid. Hence, I'm trying to do follow Change WPF DataTemplate for ListBox item if selected. But for Xamarin, there's no ListBoxItem as there's only a ListView.
On a further note, I am working on Android and iOS. An example that is cross-platform to solve this will be greatly appreciated. Below are parts of my code.
datatemplate.cs
class MessageTemplateSelector : DataTemplateSelector
{
public MessageTemplateSelector()
{
ReceiverDataTemplate = new DataTemplate(typeof(MessageReceiver));
SenderDataTemplate = new DataTemplate(typeof(MessageSender));
}
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
var message = item as Message;
if (message == null)
return null;
return message.isSender ? ReceiverDataTemplate : SenderDataTemplate;
}
private readonly DataTemplate ReceiverDataTemplate;
private readonly DataTemplate SenderDataTemplate;
}
MessageSender.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Project.layout.MessageSender">
<ViewCell.View>
<Grid HorizontalOptions="EndAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="{Binding Path=Height}"/>
<RowDefinition Height="*" />
<RowDefinition Height="{Binding Path=Height}" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="15" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Path=timestamp}" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3" HorizontalTextAlignment="Center" HorizontalOptions="Center" VerticalOptions="Center" IsVisible="{Binding Path=Selected}"/>
<Frame Padding="0" CornerRadius="20" Grid.Column="1" Grid.Row="1" HorizontalOptions="EndAndExpand" >
<Grid BackgroundColor="White" VerticalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Text="{Binding Path=text}" VerticalOptions="FillAndExpand" Margin="15,10"/>
</Grid>
</Frame>
<Label Text="Seen" Grid.Column="1" Grid.Row="2" HorizontalOptions="EndAndExpand" IsVisible="{Binding Path=Selected}"/>
</Grid>
</ViewCell.View>
</ViewCell>
Message.cs
class Message
{
public bool isSender { get; set; }
public sbyte status { get; set; }
public string text { get; set; }
public string timestamp { get; set; }
public Message(bool isSender, sbyte status, string text, string timestamp)
{
this.isSender = isSender;
this.status = status;
this.text = text;
this.timestamp = timestamp;
}
public sbyte height = 0;
public sbyte Height { get { return height; } set { height = value; } }
bool selected = false;
public bool Selected
{
get { return selected; }
set { selected = value;if (value) { Height = 25; } else { Height = 0; } }
}
}
Show mainpage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Project.model"
x:Class="Project.MainPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:MessageTemplateSelector x:Key="MessageTemplateSelector"></local:MessageTemplateSelector>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<ListView x:Name="conversation"
ItemTemplate="{StaticResource MessageTemplateSelector}"
ItemsSource="{Binding Message}"
HasUnevenRows="True"
SeparatorVisibility="None"
RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToParent,Property=Height,Factor=1,Constant=0}"
IsPullToRefreshEnabled="true"
ItemTapped="Conversation_ItemTapped"
Refreshing="Conversation_Refreshing">
</ListView>
</ContentPage>
MainPage.cs
private void Conversation_ItemTapped(object sender, ItemTappedEventArgs e)
{
if (e.Item == null) return;
Message selectedItem = (Message)e.Item;
Log.Debug("ItemTap","Height before:" + selectedItem.Height);
if (selectedItem.Selected) { ((ListView)sender).SelectedItem = null; selectedItem.Selected = false; }
else { selectedItem.Selected = true; }
Log.Debug("ItemTap", "Height after:" + selectedItem.Height);
}
This is a screenshot of my log that is present in the ItemTapped event in my ListView. As you can see, the height updates but it's not reflecting on the ListView.
I am teaching myself... I cannot understand why the UI won't update when a second class is involved. I am missing something basic and I don't get it.
In the first Class:
I have two ObservableCollections bound to two WPF ListViews, which is bound correctly and works.
I have a Command bound to a Button to move items from one Collection to the other, which works as expected.
In the second Class (backcode) I have implemented "Drag and Drop". On Drop I try to call the same Method (which is in the first Class and is used by the Button/Command. The Command is also in the first class).
On "Drag and Drop" the items are moved from one collection to the other (confirmed with Console.Writeline), however the UI doesn't update like it does with the Button/Command.
I believe the problem is that with "Drag and Drop" I am calling the Method from another class. I thought I could do that, but I must not be doing it right?
I have included everything from 4 files (xaml, backcode, class, relayCommand) so hopefully it is easy to reproduce. Can anyone tell me why & how to get this to work???
<Window x:Class="MultipleClassDragAndDrop.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:MultipleClassDragAndDrop"
xmlns:ViewModel="clr-namespace:MultipleClassDragAndDrop.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="716" Width="500">
<Window.Resources>
<ViewModel:MultiColumnViewModel x:Key="MultiColumnViewModel"/>
</Window.Resources>
<Grid DataContext="{Binding Mode=OneWay, Source={StaticResource MultiColumnViewModel}}" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
<Button Content="Test Command" Command="{Binding Test_Command}"/>
</StackPanel>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView1" Background="Black" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView1, UpdateSourceTrigger=PropertyChanged}" MouseMove="ListView1_MouseMove" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightPink" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
<Grid Grid.Row="0" Grid.Column="2" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView2" Background="Black" MinHeight="300" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView2, UpdateSourceTrigger=PropertyChanged}"
MouseMove="ListView1_MouseMove"
AllowDrop="True" Drop="ListView2_Drop" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightBlue" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
BackCode
using MultipleClassDragAndDrop.ViewModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
namespace MultipleClassDragAndDrop
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
private void ListView1_MouseMove(object sender, MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
int lb_itemIndex = ListView1.SelectedIndex;
// Package the data.
DataObject data = new DataObject();
data.SetData("Int", lb_itemIndex);
data.SetData("Object", this);
// Inititate the drag-and-drop operation.
DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
}
}
private void ListView2_Drop(object sender, DragEventArgs e)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()}");
base.OnDrop(e);
int index = (int)e.Data.GetData("Int");
// Call A Method In A Different Class
objMultiColumnViewModel.AddAndRemove(index);
e.Handled = true;
}
}
}
My ViewModel Class
using MultipleClassDragAndDrop.ViewModel.Commands;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Input;
namespace MultipleClassDragAndDrop.ViewModel
{
public class ActiveJob : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
public string _JobID;
public string JobID
{
get { return _JobID; }
set
{ _JobID = value; NotifyPropertyChanged("JobID"); }
}
public string _CustomerName;
public string CustomerName
{
get { return _CustomerName; }
set
{ _CustomerName = value; NotifyPropertyChanged("CustomerName"); }
}
}
public partial class MultiColumnViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
//Test Command
private ICommand _Test_Command;
public ICommand Test_Command
{
get
{
if (_Test_Command == null)
{
_Test_Command = new RelayCommand<object>(ExecuteTest_Command, CanExecuteTest_Command);
}
return _Test_Command;
}
}
public bool CanExecuteTest_Command(object parameter)
{
return true;
}
public void ExecuteTest_Command(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
AddAndRemove(0);
Mouse.OverrideCursor = Cursors.Arrow;
}
public void AddAndRemove(int selectedIndex)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()} Index = {selectedIndex}\n");
ActiveJobListView2.Add(ActiveJobListView1[selectedIndex]);
ActiveJobListView1.RemoveAt(selectedIndex);
foreach (var item in ActiveJobListView1)
{
System.Console.WriteLine($"ActiveJobListView1: {item.JobID}, {item.CustomerName}");
}
System.Console.WriteLine($" ");
foreach (var item in ActiveJobListView2)
{
System.Console.WriteLine($"ActiveJobListView2: {item.JobID}, {item.CustomerName}");
}
}
public MultiColumnViewModel()
{
ActiveJobListView1 = new ObservableCollection<ActiveJob>();
ActiveJobListView2 = new ObservableCollection<ActiveJob>();
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB100", CustomerName = "Smith" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB101", CustomerName = "Jones" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB102", CustomerName = "Black" });
}
#region Properties
private ObservableCollection<ActiveJob> _ActiveJobListView1;
public ObservableCollection<ActiveJob> ActiveJobListView1
{
get { return _ActiveJobListView1; }
set
{
_ActiveJobListView1 = value;
NotifyPropertyChanged("ActiveJobListView1");
}
}
private ObservableCollection<ActiveJob> _ActiveJobListView2;
public ObservableCollection<ActiveJob> ActiveJobListView2
{
get { return _ActiveJobListView2; }
set
{
_ActiveJobListView2 = value;
NotifyPropertyChanged("ActiveJobListView2");
}
}
#endregion
}
}
When binding to a Collection, there are 3 kinds of ChangeNotification you need:
The Notification that informs the UI if something was added or removed from the Collection. That is the only kind of Notification ObservableCollection provides.
The Notification on the property exposing the ObservableCollection. Due to case 1 binding and the lack of a "add range", it is a bad idea to do bulk-modifications of a exposed List. Usually you create a new list and only then Expose it to the UI. In your case those would be the properties "ActiveJobListView1" and it's kind.
The Notification on every property of every type exposed in the collection. That would be "ActiveJob" in your case.
Some of those are often forgotten, with Case 2 being the most common case. I wrote a small introduction into WPF and the MVVM pattern a few years back. maybe it can help you here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2/lets-talk-about-mvvm?forum=wpf
You have issues with different instances of the same class.
Change:
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
To:
var objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
and it should work
EDIT:
What you are doing is strongly against MVVM principals.
EDIT-2
I had to do some modification to you code to make it work:
In your XAML:
<Window.DataContext>
<local:MultiColumnViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
In your MainWindow.cs:
public MainWindow()
{
InitializeComponent();
objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
}
private MultiColumnViewModel objMultiColumnViewModel;
Trying to render a ListView with a grid into it. The grid contains two columns. The first one, with a button. The second one, with a Label.
Model contains two attributes. First one, a List of specific object. Second one, a string.
Finally, the label of the grid inside of listview will be binded to the one random attribute of list of object of the Model.
My best approach is:
<ListView x:Name="intervectionList"
ItemsSource="{Binding .}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="5">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="1"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="3*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Text="Play" Grid.Row="0" Grid.Column="0" Clicked="OnLogginClicked"/>
<Label Grid.Row="0" Grid.Column="1" Text="{Binding random_attribute}"/>
<BoxView Color="Navy" HeightRequest="1" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
CODE-BEHIND:
public Intervection()
{
InitializeComponent();
var obj= new Model();
this.BindingContext = prueba.List_of_object;
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaiseOnPropertyChange([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ViewModel()
{
_viewmodel = new ViewModel();
}
private ViewModel _viewmodel;
private string _attribute1;
public string Attribute1
{
get { return _attribute1; }
set { _attribute1= value; RaiseOnPropertyChange(); }
}
.............
}
public class Model
{
public List<obj> Intervencion;
public string attribute2;
// Helpers...
}
That is not rendering anything.
I tried successive approaches. Coming from Basic ListView with string, ListView of object,... and so on. The problem is coming from when I insert the grid.
After check Stackoverflow. I found this link Create Grid from Code-Behind, but this is not serving to my purpose because I can´t re-write the view model. (I, even, tried to coded it).
As usual, thanks mates.
I have added the VerticalOptions to the Listview and Grid.
I have created a viewmodel in which I have one property, the list of items.
The viewmodel also implements INotifyPropertyChanged.
You can read about INotifyPropertyChanged here.
The INotifyPropertyChanged interface is used to notify clients,
typically binding clients, that a property value has changed.
XAML :
<ListView ItemsSource="{Binding MyObservableCollection}" VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="5" VerticalOptions="Fill">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="1"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="3*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Text="Play" Grid.Row="0" Grid.Column="0"/>
<Label Grid.Row="0" Grid.Column="1" Text="{Binding .}"/>
<BoxView Color="Navy" HeightRequest="1" Grid.Row="1"
Grid.Column="0" Grid.ColumnSpan="2"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel :
public class ListViewWithGridViewModel : INotifyPropertyChanged
{
private ObservableCollection<string> _myObservableCollection;
public ListViewWithGridViewModel()
{
MyObservableCollection = new ObservableCollection<string>(new List<string> { "abc", "xyz", "pqr", "aaa", "abc", "xyz", "pqr", "aaa", "abc", "xyz", "pqr", "aaa" });
}
public ObservableCollection<string> MyObservableCollection
{
get { return _myObservableCollection; }
set
{
_myObservableCollection = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
XAML.cs :
public partial class ListViewWithGrid : ContentPage
{
public ListViewWithGrid()
{
InitializeComponent();
BindingContext = new ListViewWithGridViewModel();
}
}
Finally I got it. The xaml needed for it is the next one (you can complet all code coming from the code´s question). Posted just in case you all want to use another approach. Proper answer (more elegant) is #Rohit´s answer.
<ContentPage.Resources>
<ResourceDictionary>
<Color x:FactoryMethod="FromHex" x:Key="fondoBlancoPalido">
<x:Arguments>
<x:String>#F2F2F2</x:String>
</x:Arguments>
</Color>
</ResourceDictionary>
</ContentPage.Resources>
<ListView x:Name="listView" ItemsSource="{Binding .}" BackgroundColor="{StaticResource fondoBlancoPalido}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Grid Padding="5">
<Grid.RowDefinitions>
<RowDefinition Height="60"></RowDefinition>
<RowDefinition Height="60"></RowDefinition>
<RowDefinition Height="10"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="2*"></ColumnDefinition>
<ColumnDefinition Width="3*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Clicked="OnStartClicked" Image="play.png" BackgroundColor="Transparent" HorizontalOptions="Center" Grid.RowSpan="2"/>
<Label Grid.Row="0" Grid.Column="1" Text="Hora de Inicio: " XAlign="Center" YAlign="Center" TextColor="Black" FontAttributes="Bold"/>
<Label Grid.Row="0" Grid.Column="2" Text="{ Binding attribute3 }" XAlign="Center" YAlign="Center" TextColor="Black"/>
<Label Grid.Row="1" Grid.Column="1" Text="Encargado de la Tarea: " XAlign="Center" YAlign="Center" TextColor="Black" FontAttributes="Bold"/>
<Label Grid.Row="1" Grid.Column="2" Text="{ Binding attribute4 }" XAlign="Center" YAlign="Center" TextColor="Black"/>
<BoxView Color="Navy" HeightRequest="2" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3"/>
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
So i've been trying to build a simple rss feed app for win10 using universal apps and mvvmcross that will do the following:
Load all items from an address in a listbox or something similar which will show them in the splitview.
When clicking on a title it will open a WebView with the full article of the specific title.
The problem I have, is binding different parts of the same RssItems to different controls but keep their relation. I'm new to both technologies but i think it should be possible somehow, I just can't find the way.
These are the related parts of the code I wrote:
viewModel.cs:
class FirstViewModel : MvxViewModel
{
private List<string> _rssItems;
public List<string> RssItems
{
get { return _rssItems; }
set { _rssItems = value; RaisePropertyChanged(() => RssItems); }
}
public MvxCommand SelectionChangedCommand
{
get
{
return new MvxCommand(() =>
{
LoadRssItems();
});
}
}
private async void LoadRssItems()
{
List<string> feedItems = new List<string>();
SyndicationClient rssReaderClient = new SyndicationClient();
SyndicationFeed rssFeed = await rssReaderClient.RetrieveFeedAsync(new Uri("xml address"));
if (rssFeed != null)
{
foreach (var item in rssFeed.Items)
{
feedItems.Add(item.Title.Text);
RssItemsOrinigal.Add(item);
}
RssItems = feedItems;
}
}
And the firstView.xaml:
<views:MvxWindowsPage
x:Class="RssReader.Views.FirstView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:RssReader.Views"
xmlns:views="using:MvvmCross.WindowsUWP.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<RelativePanel>
<Button x:Name="HamburgerButton"
RelativePanel.AlignLeftWithPanel="True"
FontFamily="Segoe MDL2 Assets"
FontSize="36"
Content=""
Click="HamburgerButton_Click"/>
<SplitView Grid.Row="1"
x:Name="sv"
DisplayMode="CompactOverlay"
OpenPaneLength="200"
CompactPaneLength="50">
<SplitView.Pane>
<ListBox x:Name="lstMenuItems" SelectionMode="Single">
<ListBoxItem x:Name="ListBoxItem1">
<StackPanel Orientation="Horizontal">
<Button FontFamily="Segoe MDL2 Assets" FontSize="36" Command="{Binding SelectionChangedCommand}"></Button>
<TextBlock FontSize="24" Text="Website 1" />
</StackPanel>
</ListBoxItem>
</ListBox>
</SplitView.Pane>
<SplitView.Content>
<ListBox ItemsSource="{Binding RssItems}"/>
</SplitView.Content>
</SplitView>
</Grid>
</views:MvxWindowsPage>
One way to do this is using ContentPresenter together with your ListBox and set the Visibility property each time when you select an item or want to go back to the ListBox:
<Page.Resources>
<DataTemplate x:Key="DetailContent">
<WebView Source="{Binding DetailUri}" />
</DataTemplate>
<DataTemplate x:Key="ListBoxContent">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</Page.Resources>
...
<SplitView.Content>
<Grid>
<ContentPresenter Content="{x:Bind listBox.SelectedItem, Mode=OneWay}"
ContentTemplate="{StaticResource DetailContent}" Visibility="{x:Bind VM.IsVisible, Mode=OneWay}" />
<ListBox x:Name="listBox" ItemsSource="{x:Bind VM.RssItems}"
ItemTemplate="{StaticResource ListBoxContent}" SelectionChanged="{x:Bind VM.listBox_SelectionChanged}" />
<Button Content="Close and Show ListBox" VerticalAlignment="Bottom" Visibility="{x:Bind VM.IsVisible, Mode=OneWay}"
Click="{x:Bind VM.IsVisible_Clicked}" />
</Grid>
</SplitView.Content>
In the ViewModel of this page for example:
public class Page6ViewModel : INotifyPropertyChanged
{
public ObservableCollection<ListBoxDetail> RssItems;
private Visibility _IsVisible = Visibility.Collapsed;
public Visibility IsVisible
{
get { return _IsVisible; }
set
{
if (value != _IsVisible)
{
_IsVisible = value;
OnpropertyChanged();
}
}
}
public Page6ViewModel()
{
RssItems = new ObservableCollection<ListBoxDetail>();
RssItems.Add(new ListBoxDetail { Title = "Item 1", DetailUri = "http://stackoverflow.com/questions/38853708/using-different-parts-of-rss-feed-in-a-universal-app-using-mvvmcross-and-command?noredirect=1#comment65137456_38853708" });
RssItems.Add(new ListBoxDetail { Title = "Item 2", DetailUri = "https://msdn.microsoft.com/en-us/library/windows/apps/ms668604(v=vs.105).aspx" });
RssItems.Add(new ListBoxDetail { Title = "Item 3", DetailUri = "https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.controls.itemscontrol.itemssource.aspx" });
}
private ListBox listBox;
public void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
listBox = sender as ListBox;
listBox.Visibility = Visibility.Collapsed;
IsVisible = Visibility.Visible;
}
public void IsVisible_Clicked(object sender, RoutedEventArgs e)
{
listBox.Visibility = Visibility.Visible;
IsVisible = Visibility.Collapsed;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnpropertyChanged([CallerMemberName]string propertyName = "")
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Another common method is using Frame as Content of SpiltView, then create a page for ListBox and a page for WebView, at first navigate this frame to the page of ListBox, in the SelectionChanged event, you can send the uri as parameter and navigate this frame to the page of WebView. For this method, there are many samples on the internet and you can search for them.