I'm making an app where I have layouts generate dynamically based on a list.
<StackLayout>
<CollectionView x:Name="taskList">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Task">
<VerticalStackLayout Margin="15">
<Entry Text="{Binding name}" IsReadOnly="True" />
<Entry Text="{Binding departmentsString}" IsReadOnly="True"/>
<HorizontalStackLayout>
<Entry Text="{Binding status}" IsReadOnly="True"/>
<Entry Text="{Binding deadline}" IsReadOnly="True" />
<Entry Text="{Binding author.fullName}" IsReadOnly="True"/>
</HorizontalStackLayout>
<Entry Text="{Binding description}" IsReadOnly="True" />
</VerticalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
The list is bonded like this:
taskList.ItemsSource = company.tasks;
I want to refresh this whenever I add new items to the list.
I tried rebinding the list to the ItemsSource but it just didn't work:
taskList.ItemsSource = company.tasks;
How should I do it? Can I just refresh the view so it generates everything again?
Instead of assigning the ItemsSource from code-behind, you should bind a custom collection, which makes it much easier and decouples the UI and logic. Read more over the MVVM pattern.
(I don't have a IDE here, so it's not tested)
You could do something like:
// data class
public class TaskInfo
{
public string name {get; set;}
public string departmentsString {get;set;}
}
// you page/view whatever code behind the xaml
public class YourPage : // insert the class which it is derived from.
{
// create a collection property, which generates events when changed. The UI react on these events.
public ObservableCollection<TaskInfo> Tasks {get;} = new();
public YourPage()
{
InitComponentsOrSomething();
// assign the datacontext (which the view binds to)
this.DataContext = this;
}
}
And your xaml would be something like this: (mind the Binding instead of the name)
<StackLayout>
<CollectionView ItemsSource="{Binding Tasks}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Task">
<VerticalStackLayout Margin="15">
<Entry Text="{Binding name}" IsReadOnly="True" />
<Entry Text="{Binding departmentsString}" IsReadOnly="True"/>
<HorizontalStackLayout>
<Entry Text="{Binding status}" IsReadOnly="True"/>
<Entry Text="{Binding deadline}" IsReadOnly="True" />
<Entry Text="{Binding author.fullName}" IsReadOnly="True"/>
</HorizontalStackLayout>
<Entry Text="{Binding description}" IsReadOnly="True" />
</VerticalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
If you want to add items to the collection, just use the Tasks property:
Tasks.Add(new TaskInfo { name = "some name", departmentsString = "Enter here" });
ps: I would not call a data object 'Task', either give it a better description. TaskInfo/CompanyTask.
You should use an IEnumerable collection that sends property change notifications(such as ObservableCollection) for the ItemsSource, then simply add to the underlying collection.
Here's an example:
ObservableCollection<string> tasks = new();
taskList.ItemsSource = tasks;
tasks.Add("Do something");
Microsoft Reference
Related
I am struggling to bind my user-defined tooltip to the items of a TreeView.
This is the XAML inside the TreeView: as you can see I bind my HierarchicalDataTemplate to the GroupWrapper (simple Class that exposes the properties Name and Children) and the DataTemplate to the DocWrapper (again, simple Class that has properties such as Name, Icon, BsonContent).
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type tmistruct:GroupWrapper}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type tmistruct:DocWrapper}" >
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Icon}"/>
<TextBlock Text="{Binding Name}" Tag="{Binding BsonContent}" VerticalAlignment="Center" Style="{StaticResource TreeViewTextboxItemStyle}"/>
<StackPanel.ToolTip>
<local:MyDocTooltip
NameField="{Binding Name}"/>
</StackPanel.ToolTip>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
It all works great! The Tree is automatically populated with the correct text and icons for all nodes BUT... my user-defined tooltip doesn't get the memo. The tooltip shows, but the name field is not populated. Note that if I replace
<local:MyDocTooltip
NameField="{Binding Name}"/>
with:
<local:MyDocTooltip
NameField="TESTSTRING"/>
The test string is correctly displayed in the Tooltip.
This is what my user-defined tooltip looks like:
namespace MyControls
{
/// <summary>
/// Interaction logic for MyDocTooltip.xaml
/// </summary>
public partial class MyDocTooltip : UserControl
{
public MyDocTooltip()
{
InitializeComponent();
DataContext = this;
}
public static readonly DependencyProperty NameFieldProperty = DependencyProperty.Register("NameField", typeof(string), typeof(MyDocTooltip));
public string NameField
{
get { return (string)GetValue(NameFieldProperty); }
set { SetValue(NameFieldProperty, value); }
}
}
}
I guess something to do with my tooltip's data context being set to its own ViewModel? I've been trying with relative references but with no luck. Any help appreciated! Cheers.
OK my whole setup was wrong. (Thanks #ASh)
For those out there who are struggling with similar issues:
Each user control should not set its DataContext property!
This should be inherited.
What you do inside each user control, is to bind each element to its UserControl ancestor.
So, inside MyDocTooltip.xaml each element will be defined like this (bound to NameField property):
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},Path=NameField}"/>
Then I do the same in the TreeView (which is also inside a user control):
<TreeView x:Name="treeViewCollection" Grid.Row="2" Grid.ColumnSpan="2" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},Path=FilteredGroups, UpdateSourceTrigger=PropertyChanged}" >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type tmistruct:GroupWrapper}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<DataTemplate x:Name="TreeViewDataTemplate" DataType="{x:Type tmistruct:DocWrapper}" >
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Icon}"/>
<TextBlock Text="{Binding Name}" Tag="{Binding BsonContent}" VerticalAlignment="Center"/>
<StackPanel.ToolTip>
<local:MyDocTooltip
NameField="{Binding Name}"
/>
</StackPanel.ToolTip>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
And it all seems to work fine. Please do let me know if I'm still doing it wrong. Thanks for the help.
I have a problem. I created a CollectionView with a ViewModel, in that ViewModel I have 2 different Lists. Now I know how to show data from 1 list, but now I want in the datatemplate a picker with the data of the second list. Here is the xaml code:
<CollectionView ItemsSource="{Binding imageList}">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Picker Grid.Row="0" Grid.RowSpan="2" Grid.Column="3" Title="Formaat" ItemsSource="{Binding secondList}" HorizontalOptions="FillAndExpand" />
<Label Grid.Row="0" Grid.Column="4" VerticalTextAlignment="Center" VerticalOptions="Center"
HorizontalOptions="Center" Text="{Binding Price, StringFormat='€ {0:F2}'}" TextColor="Black"
FontSize="18" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
So the Price does get shown from the imageList, but the Picker is empty. In the Picker I want the element Name from the secondList
How can I fix this?
The problem is that your BindingContext in the DataTemplate of your CollectionView element is an item from your imageList. Therefore the binding will not work, as I assume your secondList is not a part of the element's model that you're using in the imageList but rather a part of the entire view model used for the page.
I would suggest using the RelativeBinding. I don't know the names of your models but your Picker binding could look like this:
<Picker ItemsSource="{Binding Source={RelativeSource AncestorType={x:Type local:YourViewModelTypeName}}, Path=secondList}" />
I'm working on my first Xamarin app. I want to make a listview with grouping and it succeeded. The only problem I have that it won't scroll. My other listview on another page scrolls without a problem, but my listview with grouping won't do that. I tried this both on an Android simulator as on my Android phone (I don't have a macbook or something to test on iOS), and it won't scroll on either of them. I looked it up but a lot of people put the listview in a scrollview, and I didn't do that.
This is my XAML code:
<StackLayout Margin="10" Orientation="Vertical">
<Label Text="{Binding Title}" FontAttributes="Bold"
FontSize="20" HorizontalOptions="CenterAndExpand" />
<Label Text="{Binding Subtitle}" FontAttributes="Italic"
FontSize="15" HorizontalOptions="CenterAndExpand" />
<ListView HasUnevenRows="True" SeparatorColor="Accent"
VerticalOptions="FillAndExpand" IsEnabled="False"
IsGroupingEnabled="True" GroupDisplayBinding="{Binding Key}"
ItemsSource="{Binding ScansGroup}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Spacing="4">
<StackLayout Orientation="Horizontal" Margin="10,7,10,1">
<Label Text="{Binding Location, StringFormat='{0}'}"
FontAttributes="Bold" FontSize="16" />
<Label Text="{Binding DateTime, StringFormat='{0:dd/MM/y HH:mm}'}"
HorizontalOptions="EndAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding ElapsedTimeOnLocation}"
HorizontalOptions="Start"
Margin="10,0,10,7" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
I put it in an MVVM structure, and the grouping I did with a MVVM helper that I got from here https://channel9.msdn.com/Shows/XamarinShow/The-Xamarin-Show-12-MVVM-Helpers .
My code behind that is meant for the grouping is the following:
public ObservableRangeCollection<Grouping<string, ScanViewModel>> ScansGroup { get; } =
new ObservableRangeCollection<Grouping<string, ScanViewModel>>();
void Group()
{
var grouped = from scan in Scans
group scan by scan.Day into scanGroup
select new Grouping<string, ScanViewModel>(scanGroup.Key, scanGroup);
ScansGroup.ReplaceRange(grouped);
}
The grouping shows perfectly and the list too. The only problem is that
I can't scroll. Can someone help me?
I've figured it out. There's nothing wrong with your grouping. In your code you set IsEnabled="False" to the ListView. It makes the listview's scroll ability to be handled just if you drag from an enabled item inside the ListView, and even thus, the scroll is like belittled and with a very bad user experience.
Just set your ListView like this:
<ListView HasUnevenRows="True"
SeparatorColor="Accent"
VerticalOptions="FillAndExpand"
IsEnabled="True"
IsGroupingEnabled="True"
GroupDisplayBinding="{Binding Key}"
ItemsSource="{Binding ScansGroup}">
...
</ListView>
I can't tell you if it was designed to be this way, or if it 's a bug, but it is the cause of your issue.
It's probable you have done this to handle some unwanted behavior. If so, let us know what is specifically your intend with this IsEnabled="False", then we'll be able to help you with this.
I hope it helps you.
You didn't add the template for the group. Add this in the <ListView>
<ListView.GroupHeaderTemplate >
<DataTemplate >
<ViewCell Height="28" >
<Label Text="{Binding GroupName}" VerticalTextAlignment="Center" HorizontalOptions="Start" />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
Also, in the code bend or ViewModel, you need to bind to a group class of the items you want in the list such as:
public class GroupDetails : ObservableCollection<SomeItemsToShow>
{
public string GroupName { get; set; }
}
Check out this Example for more details:
https://xamarinhelp.com/xamarin-forms-listview-grouping/
I have used event to command behaviours on an entry field to trigger a command that disables another on screen entry field when the users starts typing in the current entry field.
To be clear the event to command behaviour is working fine and so is the binding, meaning that I have put debug points on both and they are hit when the code is executed. The problem is that even though they are hit the IsEnabled property does not change on the other entry field.
The code:
The Xaml:
<Entry
PlaceholderColor="Black"
TextColor="Black"
Text="{Binding Identity, Mode=OneWayToSource}"
Placeholder="Driver ID"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="1">
<Entry.Behaviors>
<behaviors:FormEventToCommandBehavior EventName="TextChanged" Command="{Binding TextEntryTrigger}"/>
</Entry.Behaviors>
</Entry>
<Label Text="OR" TextColor="Black"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="2"
VerticalOptions="Center"
/>
<Entry
PlaceholderColor="Black"
TextColor="Black"
Text="{Binding Identity, Mode=OneWayToSource}"
Placeholder="Route ID"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="3"
IsEnabled="{Binding RouteIDVis}"/>
The ViewModel:
These command and properties are set in the right place I have just put them here for brevity.
public ICommand TextEntryTrigger { get; protected set; }
TextEntryTrigger = new Command(() => HandleEntryFieldVisibility());
public bool RouteIDVis
{
get { return _routeidvis; }
set
{
_routeidvis = value;
OnPropertyChanged(nameof(RouteIDVis));
}
}
private void HandleEntryFieldVisibility()
{
RouteIDVis = false;
}
I have left out the code for the event to command because as I stated when putting debug points in all of this they all get hit and I can see the RouteIDVis is being set to false but it does not disable the second entry field.
I have looked around but I cant seem to find a straight answer just some one sentence answers and no proper code answers.
Any ideas?
Have you tried using the solution from
https://forums.xamarin.com/discussion/71527/problem-with-binding-isenabled-property-of-an-entry
It make use of 2 Entry controls. The first Entry control is disabled while the second Entry control is enabled. The IsVisible ensures only one control is visible.
<ContentView Grid.Row="0"
Grid.Column="1"
IsVisible="{Binding EntryEnabled}">
<Entry IsEnabled="True"
Text="{Binding PropertyValue, Mode=TwoWay}"/>
</ContentView>
<ContentView Grid.Row="0"
Grid.Column="1"
IsVisible="{Binding EntryEnabled, Converter={StaticResource InverseConverter}}">
<Entry IsEnabled="False"
Text="{Binding PropertyValue, Mode=TwoWay}"/>
</ContentView>
<Entry Text="{Binding PropertyValue, Mode=TwoWay}"
IsEnabled="False"/>
Change order of property, that may be a problem. It happened to me when using IsEnabled with button.
My XAML:
<ListView x:Name="lvAlbums" ItemsSource="{Binding XPath=/downloads/Album}" SelectionMode="Multiple">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="spAlbum">
<!-- Displays first level attributes -->
<TextBlock x:Name="AlbumName" Text="{Binding XPath=#Name}"/>
<TextBlock x:Name="AlbumArtist" Text="{Binding XPath=#Artist}"/>
<ListView x:Name="lvTracks" ItemsSource="{Binding XPath=Item}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="spTrack">
<!-- Displays the second level attributes -->
<TextBlock x:Name="trackName" Text="{Binding XPath=#Name}"/>
<ProgressBar x:Name="pb1" Minimum="0" Maximum="100" Height="5px" Margin="0,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
It populates just fine from an xml file. I have an album and listed underneath are the tracks and a progress bar for each track.
I am now looping though the XML to download each track. How do I refer to the progressbar?
(ProgressBar)lvAlbums[curAlbum].lvTracks[curTrack].spTrack["pb1"]
Something like that.
Please see if this works for you :
var innerListView = lvAlbums.ItemTemplate.FindName("lvTracks", lvAlbums) as ListView;
ProgressBar pbar = innerListView.ItemTemplate.FindName("pb1", innerListView ) as ProgressBar;
Instead of trying to reference the progressbar control, I simply bound data to it and updated the data:
<ProgressBar x:Name="pb1" Value="{Binding XPath=#Progress, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Minimum="0" Maximum="100" Height="5px" Margin="0,0,0,0" Width="145"/>
Works as expected.