I've got a list of objects that I'm binding to a ListView and using a DataTemplate to show in a Xamarin app. So far, pretty simple. But the kicker is that I want one of the controls (a label specifically) to update continually.
So far this is what I have...
<ListView x:Name="ItemsListView"
ItemsSource="{Binding Items}"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
RefreshCommand="{Binding LoadItemsCommand}"
IsPullToRefreshEnabled="true"
IsRefreshing="{Binding IsBusy, Mode=OneWay}"
CachingStrategy="RecycleElement"
ItemSelected="OnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="10">
<Label Text="{Binding Title}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16" />
<mycountdown:TimerLabel Text="{Binding TimeRemainingString}"
Style="{DynamicResource ListItemDetailTextStyle}"
FontSize="13" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And the code behind for my TimerLabel class...
public class TimerLabel : Label
{
private bool beating;
public TimerLabel()
{
StartHeartbeat();
}
public void StartHeartbeat()
{
//only start beating again if not currently beating.
if (!beating)
{
beating = true;
Heartbeat();
}
}
public void StopHeartbeat()
{
beating = false;
}
async void Heartbeat()
{
while (beating)
{
this.Text = DateTime.UtcNow.ToLongTimeString();
}
}
}
This works, but the problem is that the heartbeat for each item in the list keeps running even when I navigate away from the page.
Ideally, the heartbeat would only run for items that are visible on screen, but I would settle for letting them all run and just disabling them when I leave the page.
The problem is I can't figure out how to access the StopHeartbeat() method from the page's code behind or the view model.
It's not going to be limited to only Labels either. I will end up having a few other controls that need to update in the UI thread continuously, but this is the simplest one to start with.
If there's another way I should be doing this, please say so.
Thanks!
if you are Bind List in ListItem Source then please change it from List to ObservableCollection it will work let me know if any question or can you upload viewModel code as well
Answered by the top comment in my post.
Related
I'm developing a WPF app using MVVM pattern with Caliburn.Micro
I have a config file that contains positions of where XAML elements should be inside of a StackPanel
# in this case RU_ELEMENT should be at the top, EN_ELEMENT second and DE_ELEMENT last
EN_ELEMENT = 1
DE_ELEMENT = 2
RU_ELEMENT = 0
This seems to be pretty basic yet I'm unable to find a way to do this. I found this thread: change the children index of stackpanel in wpf but changing it this way seems to be too complicated for what I am after. I just need to set an index of an element from a variable. I feel like there should be a much simpler way. I'm also ok with using some other, perhaps more appropriate layout panel than StackPanel.
XAML:
<!-- Language1 -->
<TextBlock Text="English" Foreground="DarkGray" FontSize="16"/>
<TextBox
VerticalAlignment="Top"
Height="150"
Text="{Binding SelectedItem.ValueEN, UpdateSourceTrigger=PropertyChanged}"
cm:Message.Attach="[Event GotFocus] = [Action FocusedTextBox('english')]" />
<!-- Language2 -->
<TextBlock Text="German" Foreground="DarkGray" FontSize="16"/>
<TextBox
VerticalAlignment="Top"
Height="150"
Text="{Binding SelectedItem.ValueDE, UpdateSourceTrigger=PropertyChanged}"
cm:Message.Attach="[Event GotFocus] = [Action FocusedTextBox('german')]" />
On a side note: I find WPF and C# in general to have much less discussions and "how to" guides than all of my previous languages (Java, Python, JS) so researching things online is usually a dead end for me. I'm not sure to why that is since C# is a very popular language but I'm really struggling with finding help online.
A solution could be to use an ItemsControl that would host the xaml elements. You can bind the items like <ItemsControl ItemsSource="{Binding ListOfItems} ...
Then you could easily sort the items in the corresponding ViewModel. Like so:
public BindableCollection<YourElement> ListOfItems {get;set;}
...
ListOfItems.Sort()
Note that YourElement class should have a comparator.
EDIT: As per request I'll explain it more detailed:
In your Xaml you have to declare a ItemsControl like so:
<ItemsControl ItemsSource="{Binding ListOfItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Language}" Foreground="DarkGray" FontSize="16"/>
<TextBox
VerticalAlignment="Top"
Height="150"
Text="{Binding TextValue, UpdateSourceTrigger=PropertyChanged}"
cm:Message.Attach="[Event GotFocus] = [Action FocusedTextBox($this)]" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And in your backend you should first create a class that's going to represent your item in the ItemsControl. For example:
public Class MyItem{
public string Language {get;set;}
public string TextValue {get;set;}
}
Finally in your ViewModel you'll need to create the list of items that you bind with the ItemsControl like so:
public BindableCollection<MyItem> ListOfItems {get;set;}= new BindableCollection<MyItem>();
//here you can add them in the order that is specified by the config file
public void LoadItems(){
ListOfItems.Add(new MyItem{Language="English"});
ListOfItems.Add(new MyItem{Language="Russian"});
ListOfItems.Add(new MyItem{Language="German"});
}
public void FocusedTextBox(MyItem item){
//do here whatever you want
}
I have following list view (the item source is set outside and a list of strings):
<?xml version="1.0" encoding="utf-8" ?>
<ListView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XXX.EditItemsList">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding .}"/>
<Button Text="Delete"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
With clicking the button, I want to delete the current item (string) in the list. How is this possible?
Thanks for your help :)
In principle i think the answer given by #Krzysztof Skowronek is right, I will simply try to elaborate on it, and avoid the use of ViewModel since it seems you are not using it (although the use of it is a Best Practice on Xamarin Forms).
Following your own code, i wrote the following code in XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DeleteButton"
x:Class="DeleteButton.MainPage">
<ListView x:Name="listView"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding .}"/>
<Button Text="Delete" Clicked="Delete"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
About this part of the solution i would give the following comments:
Note the use of ConntentPage instead of ListView at the top of the XAML, is that intentional?
Then, note the addition of x:Name on ListView. It will be used to communicate with the ListView from the code behind.
Additionally notice the use of HasUnevenRows set to True. This causes the ListView to adjust automatically the height of the rows.
Finally see that in Button i have set the event Clicked to "Delete", which is the name of the event handler in the code behind as you will see.
On the code behind i wrote:
using System;
using System.Collections.ObjectModel;
using Xamarin.Forms;
namespace DeleteButton
{
public partial class MainPage : ContentPage
{
ObservableCollection<String> list;
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
list = new ObservableCollection<string>()
{
"Task 1", "Task 2", "Task 3", "Task 4", "Task 5",
"Task 6", "Task 7", "Task 8", "Task 9", "Task 10"
};
listView.ItemsSource = list;
}
public void Delete(Object Sender, EventArgs args)
{
Button button = (Button)Sender;
StackLayout listViewItem = (StackLayout)button.Parent;
Label label = (Label)listViewItem.Children[0];
String text = label.Text;
list.Remove(text);
}
}
}
There i define the list of strings as an ObservableCollection (ObservableCollection causes the ListView to get a notification each time it changes, so that the ListView updates its contents, see the docu for more details).
Then i set the ItemSource property of the ListView to the collection of Strings, as you have already done.
Finally comes the EventHandler Delete, called by the Click event on Button, in XAML. The algorithm here is quite simple:
First the sender is cast to a Button (we know that the object firing the event is a Button).
Then we walk up the hierarchy tree up to the StackLayout containing the Button and the Label and retrieve the first child of it, which we know is the Label.
Once we have the Label we retrieve its Text property and call the Remove method of the collection to get ride of that item.
And that's it.
Note: If i would implement this functionality myself i would rather define a collection of objects which would containt a Text property as well as an Id property in order to remove exactly the element tapped. In the code above, if the collection contains two identical strings, the EventHandler would go simply for the first occurrence.
I hope this helps you to find the right way to solve your problem.
If you don't want to use Commands, you can use the Button's Clicked event. IE,
<Button Text="Delete" Clicked="HandleDeleteButtonClicked" />
Then in your code behind file,
private void HandleDeleteButtonClicked(object sender, EventArgs e)
{
// Assuming your list ItemsSource is a list of strings
// (If its a list of some other type of object, just change the type in the (cast)):
var stringInThisCell = (string)((Button)sender).BindingContext;
// Now you can delete stringInThisCell from your list.
myList.Remove(stringInThisCell);
}
If we are willing to keep MVVM approach, then in your View, name the ContentPage (or whichever root element is there) and use it as Source to bind command:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="canaraydash.View.InviteListPage"
x:Name="InvitesView">
<ListView x:Class="XXX.EditItemsList">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding .}" />
<Button Text="Delete"
Command="{Binding Path=BindingContext.AcceptRequestCommand, Source={x:Reference InvitesView}}"
CommandParameter="{Binding .}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
and in your ViewModel, define 'AcceptRequestCommand' command!
Create a DeleteItem command with a parameter in your control (or it's ViewModel preferably) and then in xaml:
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding .}"/>
<Button Text="Delete" Command="{Binding Source={this should be your VM for the whole thing}, Path=DeleteItem}" CommandParameter="{Binding}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
In command:
list.Remove(parameter);
If the the list is obeservable, it will disappear.
Here's my solution on the problem
<DataTemplate>
<Button Text="{Binding DisplayName}" Image="tab_about.png" ContentLayout="Top,0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:AnimationViewModel}}, Path=AnimationCommand}"
CommandParameter="{Binding .}" />
</DataTemplate>
And the model view file is a normal command :
public ObservableCollection<LedAnimation> AnimationList { get; }
public Command<LedAnimation> AnimationCommand { get; }
public AnimationViewModel()
{
Title = "Animation";
AnimationList = new ObservableCollection<LedAnimation>();
AnimationCommand = new Command<LedAnimation>(OnLedAnimationTap);
_serverService.AnimationCapabilities.ForEach(x => AnimationList.Add(x));
}
private void OnLedAnimationTap(LedAnimation animation)
{
if (animation == null)
return;
Console.WriteLine($"VM:{animation.Name}");
}
<ListView x:Name="myList" ItemTapped="OnMyItemTapped" ItemsSource="{Binding myList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout> CONTENT HERE </StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
where myList is an ObservableCollection
Desired: Displays all list items immediately at once without animation
Actual: Displays list items one at a time (similar to the add item animation)
Any ideas?
(This sequential displaying of items is significantly more noticeable when the list of items within the list view is larger)
The only way I've found to do it is to use a custom renderer.
These are the lines I'm using to disable the insert, delete and reload rows animations:
if (e.OldElement != null)
{
InsertRowsAnimation = UITableViewRowAnimation.None;
DeleteRowsAnimation = UITableViewRowAnimation.None;
ReloadRowsAnimation = UITableViewRowAnimation.None;
}
This needs to be placed in your CustomListViewRenderer that is inheriting from Xamarin forms ListViewRenderer.
I put mine in the OnElementChanged event.
You can use a platform specific property to disable animations:
<ContentPage ...
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core">
<StackLayout Margin="20">
<ListView ios:ListView.RowAnimationsEnabled="false">
</ListView>
</StackLayout>
</ContentPage>
Read more here:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/platform/ios/listview-row-animations
something that I thought would be simple is turning out not to be, or I'm just not thinking hard enough :)
I have a page which I navigate to, in the OnNavigateTo event I set the SelectedIndex of a ListPicker and that works fine.
If I then touch the ListPicker and select a new value the OnNavigateTo event is fired again and the new value is overridden by the original value.
My initial thought was to simply check the parent page name and if it was the ListPicker then skip the initial setting but I can't seem to find where to get the parent page name from.
Any clues? or a better way I should be handling this?
Here's the XAML:
<toolkit:ListPicker x:Name="Status" Margin="10,549,163,-97" Header="Status" FullModeHeader="Status" ExpansionMode="FullScreenOnly" BorderBrush="Black" Foreground="Black" Grid.ColumnSpan="2" Visibility="Visible">
<toolkit:ListPicker.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</toolkit:ListPicker.ItemTemplate>
<toolkit:ListPicker.FullModeItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
FontSize="43"
FontFamily="{StaticResource PhoneFontFamilyLight}"/>
</DataTemplate>
</toolkit:ListPicker.FullModeItemTemplate>
</toolkit:ListPicker>
And here's the Loaded event:
private void AddNote_Loaded(object sender, RoutedEventArgs e)
{
this.TicketStatus.ItemsSource = ticketStatus();
string st;
if (NavigationContext.QueryString.TryGetValue("status", out st))
{
tStatus = st;
TicketStatus.SelectedIndex = GetStatus(tStatus);
}
}
Ok, worked around it but creating my own page list and manually adding and removing the pages I want to check for. Bit of a hack but it works :)
Hej
I want to create a standard holdevent. When you hold an element, there would appear some options you could chose like a new list.
How do you create this, is it just simply done with a popup or is there a smarter way?
Extra
After finding the answer, see answer below, some nice info is:
Put the context creation inside the hold event.
Then you can change to different contextmenus depending on the item. You can get the item that was holded by the following
private void StackPanel_Hold(object sender, GestureEventArgs e)
{
ItemViewModel itemViewModel = (sender as StackPanel).DataContext as ItemViewModel;
string t = itemViewModel.LineOne;
}
And
<ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Height="78" Hold="StackPanel_Hold">
<TextBlock Text="{Binding LineOne}" />
<TextBlock Text="{Binding LineTwo}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
A good link for easy implementation is also youtube link below, replicated here :
Youtube
A ContextMenu is one option..
http://blogs.msdn.com/b/msgulfcommunity/archive/2013/05/19/windows-phone-toolkit-context-menu-getting-selected-item-within-a-long-list-selector.aspx