Expandable ListView in Xamarin Forms - c#

I have the following code to display a ListView using Xamarin Forms:
App.cs
public App ()
{
MainPage = GetMainPage();
}
public static Page GetMainPage()
{
return new NavigationPage(new DrilldownListViewByItem());
}
DrilldownListViewByItem:
public class DrilldownListViewByItem : ContentPage
{
public DrilldownListViewByItem()
{
Title = "Drilldown List Using ListView";
var listView = new ListView();
listView.ItemsSource = new ListItem[] {
new ListItem {Title = "First", Description="1st item"},
new ListItem {Title = "Second", Description="2nd item"},
new ListItem {Title = "Third", Description="3rd item"}
};
listView.ItemTemplate = new DataTemplate(typeof(TextCell));
listView.ItemTemplate.SetBinding(TextCell.TextProperty, "Title");
listView.ItemTapped += async (sender, args) =>
{
var item = args.Item as ListItem;
//if (item == null) return;
//await Navigation.PushAsync(new DetailPage(item));
//listView.SelectedItem = null;
};
Content = listView;
}
}
ListItem has these properties:
public string Title { get; set; }
public string Description { get; set; }
What I want to do is to show subitems when I tab on one of the main items. These subitems should be hidden at the beggining, but they should be there, I don't want to load them every time I tab on an item. Any idea? Thanks!

Try changing the visibility of the items you want to appear and disappear based on the click event of the list item.
On iOS you may also have to change the cell height (using HeightRequest), because it is likely that it won't change on it's own. I am guessing that android will work just fine.
Hope this helps.

Related

Binding to a treeview programatically not working in UWP

I'm following this article to try and programmatically bind data to a treeview (I'm on 1903).
In a brand new UWP app, I have the following code behind:
public MainPage()
{
this.InitializeComponent();
var items = new List<Item>();
var rootItem = new Item();
rootItem.Name = "Root Item";
rootItem.Children.Add(new Item() { Name = "test child 1" });
items.Add(rootItem);
var treeView = new TreeView();
treeView.ItemsSource = items;
stackPanel.Children.Add(treeView);
}
Item looks like this:
public class Item
{
public string Name { get; set; }
public ObservableCollection<Item> Children { get; set; } = new ObservableCollection<Item>();
public override string ToString()
{
return Name;
}
}
This appears to be the exact structure outlined in the above article. However, when I run the application, I get this:
My guess is that I need to do, or set something that tells this treeview, or the collection that it has children - but I can't see what that might be.
You should create an ItemTemplate as explained in the docs.
You could use the XamlReader class to do this programmatically. Something like this:
const string Xaml = "<DataTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"><TreeViewItem ItemsSource=\"{Binding Children}\" Content=\"{Binding Name}\"/></DataTemplate>";
treeView.ItemTemplate = XamlReader.Load(Xaml) as DataTemplate;
If you use C# to build a TreeView, I recommend adding a TreeViewNode using traversal.
Due to the lack of instructions, TreeView does not automatically handle the Children of the Item. In the documentation you provide, the TreeView has a DataTemplate directive, so the children can render.
You can change code like this:
public MainPage()
{
this.InitializeComponent();
var items = new List<Item>();
var rootItem = new Item();
rootItem.Name = "Root Item";
rootItem.Children.Add(new Item() { Name = "test child 1" });
items.Add(rootItem);
var treeView = new TreeView();
foreach (var root in items)
{
var rootNode = new TreeViewNode() { Content = root.Name };
if (root.Children.Count > 0)
{
foreach (var child in root.Children)
{
rootNode.Children.Add(new TreeViewNode() { Content = child.Name });
}
}
treeView.RootNodes.Add(rootNode);
}
stackPanel.Children.Add(treeView);
}
Best regards.

My code-generated ListBox's items isn't showing any content

I have a code-generated window in WPF, (using dotnet core 3.0 preview 6), and when running my application gets the correct data, and the ListBox is populated with the correct number of rows, but none of them contains values
This is a test-project I'm doing to get familiar with code generated WPF as it's needed for an upcoming project we're doing at work; I would have preferred using XAML but my lead says that that will create issues with code-re-usability.
I at first made sure I used am object which is "clean", (my entities are setup for Linq2db, so I ensured that the attributes couldn't be the culprit), then I tested binding, (just got the "Error 40" -error code, but that isn't relevant to the main problem). I have also changed the type of boxes, but It does not help, (DataGrid did work but it's not what I'm looking for in a visual).
public class ChatWindow : IChatWindow
{
private ObservableCollection<MessageDto> _observableMessages;
private readonly IMessagesRepository _messagesRepository;
public ChatWindow(IMessagesRepository messagesRepository)
{
_messagesRepository = messagesRepository;
Task.Run(async () => { await Updater(); }).ConfigureAwait(false);
}
public async Task ShowAsync(User user)
{
var chatLog = new ListBox()
{
Name = "Chatview",
ItemsSource = _observableMessages,
ItemTemplate = new DataTemplate(typeof(MessageDto)),
DataContext = _observableMessages
};
//var myBinding = new Binding("_observableMessages");
//myBinding.Source = _observableMessages;
//chatLog.SetBinding(ListBox.ItemsSourceProperty, myBinding);
var input = new TextBox()
{
Name = "InputField",
Background = new SolidColorBrush(Color.FromRgb(35, 35, 35))
};
var stackPanel = new StackPanel()
{
Children =
{
chatLog,
input
}
};
var window = new Window()
{
Name = "ChatWindow",
Content = stackPanel,
};
window.Show();
}
private async Task Updater()
{
while (true)
{
var messages = await _messagesRepository.GetAllMessages(1);
_observableMessages = new ObservableCollection<MessageDto>(messages.Select(m => new MessageDto()
{
Timestamp = m.Timestamp,
From = m.From,
Message = m.Message
}));
await Task.Delay(TimeSpan.FromSeconds(10));
}
}
}
class MessageDto
{
public DateTime Timestamp { get; set; }
public long From { get; set; }
public string Message { get; set; }
}
Image of the resultant window, (some styling code was removed from the example code to reduce noise)
Based on Flithor's comment, i did this and it worked perfectly:
private DataTemplate GetDataTemplate()
{
var dataTemplate = new DataTemplate(typeof(MessageDto));
FrameworkElementFactory stackPanelFactory = new FrameworkElementFactory(typeof(StackPanel));
stackPanelFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
FrameworkElementFactory timestamp = new FrameworkElementFactory(typeof(Label));
timestamp.SetBinding(Label.ContentProperty, new Binding("Timestamp"));
FrameworkElementFactory from = new FrameworkElementFactory(typeof(Label));
from.SetBinding(Label.ContentProperty, new Binding("From"));
FrameworkElementFactory message = new FrameworkElementFactory(typeof(Label));
message.SetBinding(Label.ContentProperty, new Binding("Message"));
stackPanelFactory.AppendChild(timestamp);
stackPanelFactory.AppendChild(from);
stackPanelFactory.AppendChild(message);
dataTemplate.VisualTree = stackPanelFactory;
return dataTemplate;
}

Selecting TextCell by code doesn't highlight row

When I click a TextCell in a ListView, the row is highlighted.
However, when I programatically select a row / a TextCell, the row isn't highlighted.
Therefore it isn't possible to indicate to the user which value in a ListView is currently selected unless he changes the selection by tapping a row.
Is that a bug or a missing feature, or how could I achieve the highlighting via code?
Sample code is attached below.
using MyApp.Model;
using System.Collections.Generic;
using Xamarin.Forms;
namespace MyApp
{
public class IntSelector : ContentPage
{
private ListView m_ListView;
public IntSelector(int uSelectedInt)
{
DataTemplate nTemplate = new DataTemplate(typeof(TextCell));
// We can set data bindings to our supplied objects.
nTemplate.SetBinding(TextCell.TextProperty, "String");
nTemplate.SetBinding(TextCell.DetailProperty, "Int");
List<clsStringInt> nList = new List<clsStringInt>();
clsStringInt nItem1 = new clsStringInt { String = "German", Int = 1031 };
clsStringInt nItem2 = new clsStringInt { String = "English", Int = 1033 };
clsStringInt nItem3 = new clsStringInt { String = "Spanish", Int = 1034 };
nList.Add(nItem1);
nList.Add(nItem2);
nList.Add(nItem3);
m_ListView = new ListView();
m_ListView.ItemTemplate = nTemplate;
m_ListView.ItemsSource = nList;
m_ListView.ItemSelected += this.OnSelection;
m_ListView.SelectedItem = nItem2;//this triggers the "OnSelection" event, so it works
nItem2.String = "->> " + nItem2.String; //the item's new string is display in the ListView, so that works as well
//what DOESN'T work is the highliting
this.Content = m_ListView;
}
void OnSelection(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null)
{
return; //ItemSelected is called on deselection, which results in SelectedItem being set to null
}
clsStringInt n = (clsStringInt)e.SelectedItem;
string sSelectedIntAsString = n.Int.ToString();
DisplayAlert("Item Selected", sSelectedIntAsString, "Ok");
}
}
}
namespace MyApp.Model
{
public class clsStringInt
{
public string String { get; set; }
public int Int { get; set; }
}
}
As mentioned in the comments you are testing on a UWP Forms app which this appears to be a bug specifically on that platform seeing how it works fine on Android and iOS.
I was able to work around this by setting the selected item in OnAppearing instead of in the Page constructor to get it to highlight.

Xamarin Forms add input field to each item of listview

I'm new to Xamarin Forms and I would want to add a input field (Numeric) on the right side of each item in my listview, so that I can type a quanty to my items. Somthing like a shopping list of items with the quantity of how many you want.
here is my code of my listview
List<string> item = new List<string>();
public MainPage()
{
InitializeComponent();
item.Add("Apple");
item.Add("Banana");
item.Add("Graps");
item.Add("Orange");
item.Add("Pineapple");
item.Add("Strawberry");
item.Add("Lemon");
item.Add("Mango");
item.Add("Cherry");
item.Add("Watermelon");
item.Add("Add");
var listView = new ListView
{
RowHeight = 40
};
listView.ItemsSource = item;
StackLayout layout = new StackLayout();
layout.Children.Add(listView);
listView.ItemSelected += (sender, e) =>
{
if (e.SelectedItem.ToString() == "Add")
{
var MyEntry = new Entry { Placeholder = "new item" };
layout.Children.Add(MyEntry);
MyEntry.Completed += MyEntry_Completed;
}
};
this.Content = layout;
}
private void MyEntry_Completed(object sender, EventArgs e)
{
var text = ((Entry)sender).Text;
item.Add(text);
}
I had a lot of trouble wrapping my head around databinding from the C# side in Xamarin Forms. I would recommend you checkout DataBinding to get a better understanding of this. I created a custom ViewCell for you to play around with, and a basic implementation so you can get a visual of how to implement it. You can adjust the view cell as much as needed. I just wanted to give you a bases of how to implement it. Hope this helps.
ViewCell
This is the view that displays for each list item in the list view.
using Xamarin.Forms;
namespace BountyApp.Controls
{
public class CustomViewCell : ViewCell
{
private Grid _grid = new Grid();
private Label _lbl = new Label() { HorizontalTextAlignment = TextAlignment.End, VerticalTextAlignment = TextAlignment.Center };
private Entry _entry = new Entry();
public CustomViewCell()
{
_lbl.SetBinding(Label.TextProperty, new Binding("Title"));
_grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(0.3, GridUnitType.Star) });
_grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(0.7, GridUnitType.Star) });
_grid.Children.Add(_lbl, 0, 0);
_grid.Children.Add(_entry, 1, 0);
View = _grid;
}
}
}
Implementing ViewCell
using Xamarin.Forms;
namespace BountyApp.Pages
{
public class ViewModel
{
public string Title { get; set; }
}
public class StepperPage : ContentPage
{
public ObservableCollection<ViewModel> List { get; set; }
public StepperPage()
{
List = new ObservableCollection<ViewModel>();
List.Add(new ViewModel { Title = "Apple" });
List.Add(new ViewModel { Title = "Banana" });
List.Add(new ViewModel { Title = "Graps" });
List.Add(new ViewModel { Title = "Orange" });
List.Add(new ViewModel { Title = "Pineapple" });
List.Add(new ViewModel { Title = "Strawberry" });
var listView = new ListView
{
RowHeight = 40,
ItemTemplate = new DataTemplate(typeof(CustomViewCell)),
ItemsSource = List
};
Content = listView;
}
}
}
Result

Add image to ListView C# WPF (without xaml)

How can I make an image item in ListView? I need to do it without xaml, only C#. I was surfing about 40 min about it and have nothin found.
I've tried a lot of things and i stopped here
in first column i see just "System.Windows.Controls.StackPanel".
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
GridView grView = new GridView();
GridViewColumn gwCol1 = new GridViewColumn();
GridViewColumn gwCol2 = new GridViewColumn();
gwCol1.Header = "Avatar";
gwCol2.Header = "Name";
gwCol2.DisplayMemberBinding = new Binding("name");
gwCol1.DisplayMemberBinding = new Binding("stack");
grView.Columns.Add(gwCol1);
grView.Columns.Add(gwCol2);
listView1.View = grView;
//
var list = new List<XData>();
listView1.ItemsSource = list;
//
BitmapImage image1 = new BitmapImage(new Uri(#"C:\Users\montana\OneDrive\Pictures\Saved Pictures\WVQfZqY.jpg"));
Image img = new Image();
img.Width = 100;
img.Height = 100;
img.Source = image1;
StackPanel stackPanel1 = new StackPanel();
stackPanel1.Orientation = Orientation.Horizontal;
stackPanel1.Children.Add(img);
list.Add(new XData() { name="hola", stack=stackPanel1 });
}
}
class XData
{
public string name { get; set; }
// public string url { get; set; }
public StackPanel stack { get; set; }
}
Here is something what produce wanted result.
Change XData definition to avoid having view elements there (you didn't mention MVVM, but it's good to stick to):
class XData
{
public string Name { get; set; }
public string Path { get; set; }
}
Now you can either define data template in xaml (common solution) or generate it in code behind like this:
var factory = new FrameworkElementFactory(typeof(Image));
factory.SetValue(Image.SourceProperty, new Binding(nameof(XData.Path)));
factory.SetValue(Image.WidthProperty, 100.0);
factory.SetValue(Image.HeightProperty, 100.0);
var dataTemplate = new DataTemplate { VisualTree = factory };
Data template can be as complicated as you like, but obviously defining it in xaml and then loading with FindResource() is way easier, consider to use this option instead.
And then this datatemplate has to be specified as CellTemplate like this:
GridView grView = new GridView();
grView.Columns.Add(new GridViewColumn { Header = "Avatar", CellTemplate = dataTemplate });
grView.Columns.Add(new GridViewColumn { Header = "Name", DisplayMemberBinding = new Binding(nameof(XData.Name)) });
listView1.View = grView;
Note: you shouldn't use DisplayMemberBinding for column with CellTemplate.
Now, finally, you can fill ListView by setting its ItemSource as usual:
var list = new List<XData>();
list.Add(new XData { Name = "hola", Path = #"c:\temp\1.jpg" });
list.Add(new XData { Name = "hola2", Path = #"c:\temp\2.png" });
listView1.ItemsSource = list;

Categories