I'm trying to do some basic UI and binding in UWP to get my head wrapped around it but I'm having trouble accessing a button within a listview item.
I have a Button where on clicking it, it creates a new object which is added to my ObservableCollection which then ends up adding a new item in my ListView
XMAL
<ListView Grid.Row="1" ItemsSource="{x:Bind counts}" x:Name="buttonsView" Margin="5,0" Background="White" Foreground="#FF5059AB">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Counter">
<StackPanel Orientation="Vertical">
<TextBlock Text="{x:Bind CountValue, Mode=TwoWay}" FontWeight="Black"/>
<Button Click="Increment_Click" Content="Increment"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
C#
public class Counter
{
private int count;
public Counter()
{
count = 0;
}
public int CountValue
{
get
{
return count;
}
set
{
count = value;
}
}
}
public sealed partial class MainPage : Page
{
ObservableCollection<Counter> counts = new ObservableCollection<Counter>();
public MainPage()
{
this.InitializeComponent();
}
private void Increment_Click(object sender, RoutedEventArgs e)
{
// ?
}
private void AddCounter_Click(object sender, RoutedEventArgs e)
{
counts.Add(new Counter());
}
}
That works.
But when I click on the button within the StackPanel, the Increment_Click method is called but I'm not sure what to do at that point. I would like to access the Counter object by getting the index of the ListView item and using that to index into the ObservableCollection.
How do I figure out what the index is of the ListView item that was added?
Instead of an event you should use Command and CommandParameter. You would then bind the Command to a command implemented in Counter and CommandParameter to the item (like {Binding}).
However, you can achieve your goal with Click event as well using DataContext:
private void Increment_Click(object sender, RoutedEventArgs e)
{
var button = (Button)sender;
var counter = (Counter)button.DataContext;
//...
}
Basically DataContext is by default inherited from the parent unless you specify otherwise, so in this case DataContext is the current list item.
Related
I have a CollectionView, on its data template has a progressbar. I'm able to find the respective element index of ObservableCollection but how can I reference its respective ProgressBar view? I need call method ProgressTo(), or may I simply bind the progress property to a property of the item on collection?
I'm afraid hat you can not use ProgressTo directly, because you can not access Progreeebar control in CollectionView directly.
If you still want to get ProgressBar, and call ProgressTo() method, you can consider to add Button in CollectionView datatemplate, like this:
<CollectionView ItemsSource="{Binding barmodels}" SelectionMode="Single">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding str}" />
<ProgressBar Progress="{Binding value}" />
<Button Clicked="Button_Clicked" Text="btn1" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Then you can get current ProgressBar control by Button.Click.
public partial class Page7 : ContentPage
{
public ObservableCollection<barmodel> barmodels { get; set; }
public Page7()
{
InitializeComponent();
barmodels = new ObservableCollection<barmodel>()
{
new barmodel(){str="test 1",value=0.1},
new barmodel(){str="test 2",value=0.2},
new barmodel(){str="test 3",value=0.3},
new barmodel(){str="test 4",value=0.4},
new barmodel(){str="test 5",value=0.5}
};
this.BindingContext = this;
}
private void Button_Clicked(object sender, EventArgs e)
{
// access buttonclickhandler
var buttonClickHandler = (Button)sender;
// access Parent Layout for Button
StackLayout ParentStackLayout = (StackLayout)buttonClickHandler.Parent;
ProgressBar progressbar = (ProgressBar)ParentStackLayout.Children[1];
progressbar.ProgressTo(0.75, 500, Easing.Linear);
}
}
public class barmodel
{
public string str { get; set; }
public double value { get; set; }
}
But I don't suggest you to do it, I think use binding Progress for ProgressBar is the best way.
Is it possible to create a ListView with ViewCells that will contain two Buttons and Label, first button will be "+", second "-" and a label will be a counter that will show how much "+" button has been tapped.
Then I want to be able to get from my listview an item that is binded to this viewcell and information about how much this item has been selected.
For now I created a StackLayout filled with Views thats "mocks" a Viewcells. This solution is so bad for many items because I have to create lots of Views (it takes few seconds).
So I would like to solve the problem using a ListView but I have no idea how to achive this. Or maybe you have a better solution than using a listview?
this should be trivial. First, create a data structure to hold your data
public class MyData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private double _count;
public double Count
{
get
{ return _count; }
set
{
_count = value;
NotifyPropertyChanged();
}
}
List<MyData> data { get; set; }
you will need to initialize it with as many rows as your want to display in your list. The create a template with a Label and Buttons that are bound to your Count property
<ListView x:Name="listView" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Count}" />
<Button Clicked="Increment" CommandParameter="{Binding .}" Text="+" />
<Button Clicked="Decrement" CommandParameter="{Binding .}" Text="-" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
in your code-behind
protected void Decrement(object sender, EventArgs args) {
var b = (Button)sender;
var data = (MyData)b.CommandParameter;
data.Count--;
}
protected void Increment(object sender, EventArgs args) {
var b = (Button)sender;
var data = (MyData)b.CommandParameter;
data.Count++;
}
finally, use binding or direct assignment to set the List's ItemsSourcee
listView.ItemsSource = data;
hi I'm try to develop Datalogger, so i create a menu, to Switch de Options i decided to use DataTemplates and different ViewModels.
Menu
XAML:
<Window.Resources>
<DataTemplate x:Name="GraficoVtemplate" DataType="{x:Type viewmodels:GraficoVM}">
<view:GraficoV DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Name="ListaVtemplate" DataType="{x:Type viewmodels:ListaVM}">
<view:ListaV DataContext="{Binding }"/>
</DataTemplate>
<ContentControl Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="4" Grid.RowSpan="5" Content="{Binding}"/>
This is how i change the datacontext;
XAML.CS
private void Novoteste_btn_Click(object sender, RoutedEventArgs e)
{
DataContext = new NovoTesteVM();
}
private void Lista_btn_Click(object sender, RoutedEventArgs e)
{
DataContext = new ListaVM();
}
This is the files that i have, the models are empty and the viewsmodals have some controles.Files
The problem is that when I change the menu, the content of the previous menu is not saved, that is when I select the menu "lista" and fill in a datagrid, and I go to another menu when I select again the menu "lista" the data are lost.I do not know what I need to add, or change so that the data is not lost
Thanks you for the explanation!
Edit 1:
MainWindows.xaml.cs
private void Novoteste_btn_Click(object sender, RoutedEventArgs e)
{
DataContext = NovoTesteVM.NovoTesteViewModel;
}
private void Grafico_btn_Click(object sender, RoutedEventArgs e)
{
DataContext = GraficoVM.Grafico;
}
NovoTesteVM.cs
public class NovoTesteVM
{
private static NovoTesteVM novoTesteViewModel;
public static NovoTesteVM NovoTesteViewModel
{
get
{
novoTesteViewModel = novoTesteViewModel ?? new NovoTesteVM();
return novoTesteViewModel;
}
}
}
Create container properties to keep the wiewmodel for corresponding views. It's loosing data because you are initializing new object on click.
Sample code -
private NovoTesteVM novoTesteViewModel;
public NovoTesteVM NovoTesteViewModel
{
get
{
novoTesteViewModel = novoTesteViewModel ?? new NovoTesteViewModel();
return novoTesteViewModel;
}
}
I am trying to open another view after tapping on an item in the list view.
I have tried adding a TapGestureRegonizer and even adding ViewCell with grids etc. None of these seem to work. I have added a tap gesture to a label and that seemed to work but the same does not work for list view items. This seems like a simple problem for something like list view, but there doesnt seem to be a built in functionality for this.
The Xaml:
<ListView x:Name="dataList"
ItemsSource="{Binding routeLabels}"
HasUnevenRows="True"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3">
</ListView>
The code behind:
var listviewgesture = new TapGestureRecognizer();
listviewgesture.SetBinding(TapGestureRecognizer.CommandProperty,"LoadRoutePage");
dataList.GestureRecognizers.Add(listviewgesture);
The view model:
public ICommand LoadRoutePage { get; protected set; }
public DriverDashboardViewModel(INavigation navigation,MessagDatabase database)
{
this._database = database;
this.Navigation = navigation;
this.LoadNotifications = new Command(async () => await OpenNotificationsPage());
this.LoadRoutePage = new Command(async () => await OpenRoutePage());
}
public async Task OpenRoutePage()
{
await Navigation.PushAsync(new RoutePageView());
}
Just to be clear the LoadNotifications method does work in opening a page but LoadRoutePage does not. So I know there is some level of communication between the view and viewmodel.
You should not be adding a TapGestureRecognizer to a ListView. Every cell already has events that handle tapping on them and a GestureRecognizer would probably only confuse the ListView regarding what the tap should be doing. There are a few ways to go about this.
1. SelectedItem binding
Bind a SelectedItem property to the ListView and handle your method calls in the setter of that property.
<ListView x:Name="dataList" ItemsSource="{Binding routeLabels}"
HasUnevenRows="True" Grid.Row="1" Grid.Column="0"
Grid.ColumnSpan="3" SelectedItem="{Binding SelectedItem}">
</ListView>
And in your viewmodel:
string _selectedItem;
public string SelectedItem {
get {return _selectedItem; }
set
{
_selectedItem = value;
// Additional code
}
}
2. Use the built in events ItemSelected or ItemTapped
A ListView has some events you can hook up named ItemSelected and ItemTapped. These can be caught in code-behind and can handle what you're trying to achieve.
<ListView x:Name="dataList" ItemsSource="{Binding routeLabels}"
HasUnevenRows="True" Grid.Row="1" Grid.Column="0"
Grid.ColumnSpan="3" ItemSelected="Handle_ItemSelected" ItemTapped="Handle_ItemTapped">
</ListView>
3. Use event to command binding with behaviors
Since you use viewmodels you ideally don't want these events since they're handled on the UI side. There are NuGet packages out there that can translate an event to a Command that you can handle in your viewmodel. Take a look at Corcav.Behaviors for example.
4. Create a behavior of your own
I have one I use regularly which looks like this:
public class ListViewSelectedItemBehavior : Behavior<ListView>
{
public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(ListViewSelectedItemBehavior));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public ListView AssociatedObject { get; private set; }
protected override void OnAttachedTo(ListView bindable)
{
base.OnAttachedTo(bindable);
AssociatedObject = bindable;
bindable.BindingContextChanged += OnBindingContextChanged;
bindable.ItemSelected += OnListViewItemSelected;
}
protected override void OnDetachingFrom(ListView bindable)
{
base.OnDetachingFrom(bindable);
bindable.BindingContextChanged -= OnBindingContextChanged;
bindable.ItemSelected -= OnListViewItemSelected;
AssociatedObject = null;
}
private void OnBindingContextChanged(object sender, EventArgs e)
{
OnBindingContextChanged();
}
private void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (Command == null)
return;
if (Command.CanExecute(e.SelectedItem))
Command.Execute(e.SelectedItem);
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
BindingContext = AssociatedObject.BindingContext;
}
}
To add this to your ListView you simply add a behavior to it:
<ListView x:Name="dataList" ItemsSource="{Binding routeLabels}"
HasUnevenRows="True" Grid.Row="1" Grid.Column="0"
Grid.ColumnSpan="3">
<ListView.Behaviors>
<behaviors:ListViewSelectedItemBehavior Command="{Binding ItemSelectedCommand}" />
</ListView.Behaviors>
</ListView>
In this case ItemSelectedCommand is a Command object in your ViewModel.
Not sure if I understand you correctly but you are trying to get an event going when someone taps on anelement of a listview?
If so you don't need a recognizer you simply have to add ItemTapped in your XAML:
<ListView x:Name="dataList"
ItemsSource="{Binding routeLabels}"
HasUnevenRows="True"
Grid.Row="1"
Grid.Column="0"
ItemTapped="Name of event"
Grid.ColumnSpan="3">
</ListView>
This will generate an event for you ( just do double tab when creating the ItemTapped ) and here you can place your code
You're binding a command instead of an event to the "Tapped" event. Try something like this:
code behind:
var listviewgesture = new TapGestureRecognizer();
listviewgesture.Tapped += Handle_listViewItemTapped;
dataList.GestureRecognizers.Add(listviewgesture);
ViewModel:
private void Handle_listViewItemTapped(object sender, EventArgs e)
{
viewModel.OpenRoutePage();
}
Im trying to get the previous selected tabs content when it is changed to another in a TabControl. For this i subscribe to the SelectionChanged event like so:
tabControl.SelectionChanged += getPreviousData
Then the getPreviousData method looks like this:
private void getPreviousData(object sender, SelectionChangedEventArgs e)
{
e.RemovedItems[0].something
}
Im a little unsure as to how i grab the previous tab content. The previous tab has a textbox control that i need to get the name of, when i change the tab. How can i accomplish that?
Assuming you have a XAML like that
<TabControl x:Name="tabControl" SelectionChanged="tabControl_SelectionChanged">
<TabItem Header="TabItem">
<Grid Background="#FFE5E5E5">
<TextBox Width="100" Height="23"></TextBox>
</Grid>
</TabItem>
<TabItem Header="TabItem">
<Grid Background="#FFE5E5E5">
<TextBlock x:Name="TextBlock"></TextBlock>
</Grid>
</TabItem>
</TabControl>
First option
Then you can access children of removed TabItem using this code
private void tabControl_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count != 0)
{
var tabItem = (TabItem)e.RemovedItems[0];
var content = (Grid)tabItem.Content;
var textBox = content.Children.OfType<TextBox>().First();
var text = textBox.Text;
}
}
Second option
You can name your textbox
<TextBox x:Name="TextBoxInFirstTab" Width="100" Height="23"></TextBox>
And access it using his name
var text2 = TextBoxInFirstTab.Text;
Third option
Use MVVM, check this answer MVVM: Tutorial from start to finish?
I am going to provide a simple sample, without any framework, but I suggest you to use anyone, like MVVM Light ToolKit.
Create a View Model
Implement the INotifyPropertyChanged interface
Create a property that will hold your text value, and in the set call the OnPropertyChanged
public class MyViewModel : INotifyPropertyChanged
{
private string _textInFirstTab;
public string TextInFirstTab
{
get { return _textInFirstTab; }
set
{
_textInFirstTab = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then in your Window constructor, set the DataContext property from Window, to a new instance for your MyViewModel.
public MainWindow()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
Then in your XAML set the Text attribute with a Binding expression
<TextBox x:Name="TextBox" Width="100" Height="23" Text="{Binding TextInFirstTab}"></TextBox>
And in your tabControl_SelectionChanged event, you can access the value like that:
private void tabControl_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count != 0)
{
var myViewModel = (MyViewModel)DataContext;
var text = myViewModel.TextInFirstTab;
}
}
If it is switching between existing tabs which you are after, then I would suggest simply storing the index of the selected tab in a class variable.
Sample code looks like this:
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
// variable to store index of tab which was most recently selected
private int lastSelectedTabIndex = -1;
public Form1()
{
InitializeComponent();
// initialise the last selected index
lastSelectedTabIndex = tabControl1.SelectedIndex;
}
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
// sanity check: if something went wrong, don't try and display non-existent tab data
if (lastSelectedTabIndex > -1)
{
MessageBox.Show(string.Format("Previous tab: {0} - {1}", lastSelectedTabIndex, tabControl1.TabPages[lastSelectedTabIndex].Text));
}
// store this tab as the one which was most recently selected
lastSelectedTabIndex = tabControl1.SelectedIndex;
}
}
}
This was written and tested in a simple application with one form and a TabControl added. No changes were made to the default properties.
You will, of course, have to hook into the event handler. I did so by double-clicking it in the IDE, but you could also hook in manually by adding:
this.tabControl1.SelectedIndexChanged += new System.EventHandler(this.tabControl1_SelectedIndexChanged);
to the form constructor, called "Form1()" in the example code.
Getting the name of a textbox is an unusual thing to want to do. May I ask what you are trying to achieve? There's probably a better way to do it than trying to determine the name of a control.