I am using Visual Studio 2015 with Update 1. I have created a new UWP Windows 10 application with 2 Blank Pages.
The first page has a GridView with an itemClick event. The object I am binding to the GridViewItem has a string field "Link" containing the name of the Page I will navigate to, when clicking on this GridViewItem.
private void GridView_ItemClick(object sender, ItemClickEventArgs e)
{
var link = (sender as Menu).Link;
Frame.Navigate(typeof(link));
}
But this is not possible... since "link" here is used like a type.
Is there a way to cast it, and make it work?
First of all, when you use the ItemClick event, the "sender" is not your Menu class, it is the GridView control itself. So your code var link = (sender as Menu).Link; should get a null reference exception.
Here I can provider two ways to do this work, but all of these two ways are using the SelectionChanged event like this:
private void gridView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var link = (gridView.SelectedItem as Menu).Link;
Frame.Navigate(link);
}
First one, define two properties in your Menu class like this:
public class Menu
{
public Type Link { get; set; }
public string Name { get; set; }
}
And use the GridView like this:
<GridView x:Name="gridView" ItemsSource="{x:Bind menu}" SelectionChanged="gridView_SelectionChanged">
<GridView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontSize="25" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
the code behind to add data to GridView:
private ObservableCollection<Menu> menu = new ObservableCollection<Menu>();
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
menu.Clear();
menu.Add(new Menu { Link = typeof(Link1), Name = typeof(Link1).Name });
menu.Add(new Menu { Link = typeof(Link2), Name = typeof(Link2).Name });
menu.Add(new Menu { Link = typeof(Link3), Name = typeof(Link3).Name });
}
Second one, you can just define one property in the Menu class, but use a Converter to display the name of each page.
Menu class:
public class Menu
{
public Type Link { get; set; }
}
the TypeToStringConverter converter:
public class TypeToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null)
return "Error";
var link = (value as Menu).Link;
return link.Name;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
And you can use this converter and the GridView in XAML like this:
<Page.Resources>
<local:TypeToStringConverter x:Key="cvt" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<GridView x:Name="gridView" ItemsSource="{x:Bind menu}" SelectionChanged="gridView_SelectionChanged">
<GridView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource cvt} }" FontSize="25" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
the code behind to add data to GridView:
private ObservableCollection<Menu> menu = new ObservableCollection<Menu>();
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
menu.Clear();
menu.Add(new Menu { Link = typeof(Link1) });
menu.Add(new Menu { Link = typeof(Link2) });
menu.Add(new Menu { Link = typeof(Link3) });
}
Related
I am using MVVM pattern. I am having a CollectionView whose items when selected calls a Command in ViewModel.
The First problem is that I cannot select an Item twice. I can have some workaround and make it work like setting CollectionView.SelectedItem = null.
The Second problem is that, when I set CollectionView.SelectedItem = null, SelectionChangedCommand is called twice (firstly for Selecting an Item, secondly due to setting SelectedItem = null). That is Company Selected Name = " + SelectedCompany.Name is printed twice in the ViewModel.
Here's my XAML Code:
<CollectionView ItemsSource="{Binding CompanyListItems}" SelectedItem="{Binding SelectedCompany}"
SelectionChangedCommand="{Binding CompanySelectedCommand}"
SelectionMode="Single" SelectionChanged="SelectionChanged">
SelectionChanged="SelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding Name}" />
</StackLayout>
</DataTemplate>
</CollectionView>
C# Code Behind:
void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if ((sender as CollectionView).SelectedItem != null)
(sender as CollectionView).SelectedItem = null;
}
ViewModel
public class CompaniesListPageViewModel : INotifyPropertyChanged
{
public CompaniesListPageViewModel()
{
CompanySelectedCommand = new Command<SelectionChangedEventArgs>(execute: (SelectionChangedEventArgs args) => CompanySelected(args));
}
public CompanyListItem SelectedCompany
{
get { return selectedCompany; }
set
{
if (value != null)
{
selectedCompany = value;
OnPropertyChanged(nameof(SelectedCompany));
}
}
}
CompanyListItem selectedCompany;
void CompanySelected(SelectionChangedEventArgs args)
{
Debug.WriteLine("Company Selected Name = " + SelectedCompany.Name);
Navigation.PushAsync(new CompanyDetailPage(SelectedCompany));
}
}
Since you have used SelectionChanged in Code Behind, so you don't need SelectionChangedCommand in your xaml.
please refer to the following code,I tested on sample code VerticalListSingleSelectionPage.xaml.cs, and it works properly on my side.
<CollectionView ItemsSource="{Binding Monkeys}"
SelectionMode="Single"
SelectionChanged="OnCollectionViewSelectionChanged">
And code behind:
void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var MyCollectionView = sender as CollectionView;
if (MyCollectionView.SelectedItem == null)
return;
UpdateSelectionData(e.PreviousSelection, e.CurrentSelection);
//clear selection
MyCollectionView.SelectedItem = null;
}
void UpdateSelectionData(IEnumerable<object> previousSelectedItems, IEnumerable<object> currentSelectedItems)
{
string previous = (previousSelectedItems.FirstOrDefault() as Monkey)?.Name;
string current = (currentSelectedItems.FirstOrDefault() as Monkey)?.Name;
Monkey currentMonkey = (currentSelectedItems.FirstOrDefault() as Monkey);
if (currentMonkey!=null) {
Navigation.PushAsync(new DetailPage(currentMonkey));
}
}
Update:
I want to do it using MVVM. Hence I want to navigate to another page
from my ViewModel class.
For this, you can do refer the following code:
async void MonkeySelectionChangedAsync()
{
if (SelectedMonkey!=null) {
SelectedMonkeyMessage = $"Selection {selectionCount}: {SelectedMonkey.Name}";
OnPropertyChanged("SelectedMonkeyMessage");
selectionCount++;
System.Diagnostics.Debug.WriteLine("-----------> " + SelectedMonkey.Name);
await Application.Current.MainPage.Navigation.PushAsync(new DetailPage(SelectedMonkey));
}
}
And you need init selectedMonkey to nullat first at the constructor of your viewmodel.
public MonkeysViewModel()
{
selectedMonkey = null;
// other code
}
After that, assign SelectedItem to null in function OnAppearing:
protected override void OnAppearing()
{
base.OnAppearing();
//mCollectionView is the `x:Name` of your `CollectionView`
mCollectionView.SelectedItem = null;
}
And the xaml is:
<CollectionView
x:Name="mCollectionView"
ItemsSource="{Binding Monkeys}"
SelectionMode="Single"
SelectedItem="{Binding SelectedMonkey}"
SelectionChangedCommand="{Binding MonkeySelectionChangedCommand}"
>
Note: You can modify your code based on the above code.
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.
You know when you stare at something long enough it doesn't really make sense any more... I am trying to bind the background property of a listview item to whether it is part of a collection in the viewmodel. Here is a simplified version of what I'm working with:
<Grid>
<ListView x:Name="AirportListView"
SelectionMode="None"
IsItemClickEnabled="True"
ItemClick="AirportListView_ItemClick">
<ListView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<Grid Padding="16">
<TextBlock Text="{x:Bind}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
and:
public sealed partial class MainPage : Page
{
public ObservableCollection<string> MyAirports { get; set; } = new ObservableCollection<string>();
public MainPage()
{
this.InitializeComponent();
AirportListView.ItemsSource = new List<string>()
{
"EDDB",
"LGIR",
"EHAM",
"LFPO",
"EGKK"
};
}
private void AirportListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is string clickedAirport)
{
if (MyAirports.Contains(clickedAirport))
MyAirports.Remove(clickedAirport);
else
MyAirports.Add(clickedAirport);
}
}
}
Ideally what I would like to achieve is to bind the background of the grid in the datatemplate so that it is a different colour when an item is part of MyAirports. I just haven't been able to figure out how to do this using x:Bind or Binding. I can think of a few more long winded ways to achieve the same thing but wondered if there is perhaps a more elegant solution with data binding.
Any thoughts would be much appreciated!
Will
You need to make the collection that goes to ItemsSource a richer class than just a string. It should contain the property IsMyAirport, then it can be bound to each ListViewItem as well to update its background.
<Page ...
xmlns:local="using:UwpQuestions.Views"
xmlns:common="using:UwpQuestions.Common">
<Page.Resources>
<common:MyBgConverter x:Key="myBgConverter"/>
</Page.Resources>
<Grid>
<ListView x:Name="AirportList"
SelectionMode="None"
IsItemClickEnabled="True"
HorizontalContentAlignment="Stretch"
ItemsSource="{x:Bind Airports}"
ItemClick="AirportListView_ItemClick">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:AirportItem">
<Grid Padding="16" Width="150" Background="{x:Bind IsMyAirport, Mode=OneWay, Converter={StaticResource myBgConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
With the following code behind:
public sealed partial class AirportListView : Page
{
public ObservableCollection<string> MyAirports { get; set; } = new ObservableCollection<string>();
public ObservableCollection<AirportItem> Airports { get; set; }
public AirportListView()
{
this.InitializeComponent();
Airports = new ObservableCollection<AirportItem>
{
new AirportItem {Name = "EDDB"},
new AirportItem {Name = "LGIR"},
new AirportItem {Name = "LFPO"},
new AirportItem {Name = "EGKK"}
};
}
private void AirportListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is AirportItem clickedAirport)
{
if (MyAirports.Contains(clickedAirport.Name))
{
MyAirports.Remove(clickedAirport.Name);
clickedAirport.IsMyAirport = false;
}
else
{
MyAirports.Add(clickedAirport.Name);
clickedAirport.IsMyAirport = true;
}
}
}
}
public class AirportItem : BindableBase
{
private string _name;
public string Name
{
get { return _name; }
set { SetProperty<string>(ref _name, value); }
}
private bool _isMyAirport = false;
public bool IsMyAirport
{
get { return _isMyAirport; }
set { SetProperty<bool>(ref _isMyAirport, value); }
}
}
AirportItem uses BindableBase to notify the ListView of when properties in the class change. It implements INotifyPropertyChanged. If you're not familiar with it, you should read up on XAML databinding.
And, it also uses the MyBgConverter (that Laith defined) to change the bool value into a different background color.
Finally, with the IsMyAirport property on the class, you may not need a separate MyAirports string list. I didn't remove it because I wasn't sure if you were using it for something else. But your logic in AirportListView_ItemClick can be changed to just use the IsMyAirport bool rather than the MyAirports list.
Create a converter that converts bool to a SolidColorBrush and use that in the binding.
<ListView Background="{x:Bind IsPartOfMyAirports, Converter={StaticResource MyBgConverter}}">
...
</ListView>
Your view model would be responsible for notifying the change for IsPartOfMyAirports.
Your converter would look something like this:
public class MyBgConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var b = (bool)value;
var color = b ? Colors.Yellow : Colors.Green;
return new SolidColorBrush(color);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
I have got myself in a bind. I'm trying to bind to a 3rd level list(basically the hierarchy is Food->Veges->Carrots). So my idea is that when you click a page of food, it brings up different subcategories of food, and for example if you select vegetables, it brings up different vegetables, and say for example you click carrot, it brings up different types of carrots based on your selection...and so on, I've been able to bind to the 2nd hierarchy(veges), but can't get to the third hierarchy based on selection. Your help would be appreciated..This is an idea of my classes:
public class Food: INotifyPropertyChanged
{
public string FoodName {get;set;}
private List<Vegetable> _veges = new List<Vegetable>();
public List<Vegetable> Veges
{
get
{
return _veges;
}
set
{
if (value != _veges)
{
_veges = value;
NotifyPropertyChanged("Veges");
}
}
}
}
Then the Vegetable class is like so:
public class Vegetable: INotifyPropertyChanged
{
public string VegeName {get;set;}
private List<Carrots> _carrot = new List<Carrots>();
public List<Carrots> Carrot
{
get
{
return _carrot;
}
set
{
if (value != _carrot)
{
_carrot = value;
NotifyPropertyChanged("Carrot");
}
}
}
}
The carrot class is similar:
Public class Carrot: INotifyPropertyChanged
{
public string CarrotTypeName {get;set;}
private List<CarrotType> _carrottype = new List<CarrotType>();
public List<CarrotType> CarrotT
{
get
{
return _carrottype;
}
set
{
if (value != _carrottype)
{
_carrottype = value;
NotifyPropertyChanged("CarrotT");
}
}
}
}
Now, in the code behind I'm binding to a list of Foods, like so, so that it gets the exact food hierarchy from the first page, NB: Items is a list of food that contains Subparts(Foods->Veges->carrots):
public partial class Subpart : PhoneApplicationPage
{
Food ourItem;
public Subpart()
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
string selectedIndex = "";
if (NavigationContext.QueryString.TryGetValue("selectedItem", out selectedIndex))
{
int index = int.Parse(selectedIndex);
ourItem = App.ViewModel.Items[index];
DataContext = ourItem;
}
}
}
And finally, my xaml binding for the 3rd page:
<Grid x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<ScrollViewer>
<ListBox x:Name="FileList"
ItemsSource="{Binding Path=Carrot}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource PhoneTextLargeStyle}"
x:Name="ContentText"
Text="{Binding CarrotTypeName}"
TextWrapping="Wrap" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
</Grid>
I'm trying to bind to the CarrotTypeName of a particular Carrot in a list of a vegetables which is in a list of Food( something like that).When I run the code, the index in the code is selecting based on the Items(list of food), not from the veges. Thanks if you understood my challenge.
The solution was to add an ID property to each of the classes(food, vegetable,carrots). Then in the SelectionChanged event of the Vege.xaml, i did this:
private void VegeListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// If selected index is -1 (no selection) do nothing
if (VegeListBox.SelectedIndex == -1)
return;
//Make the selected item in the VegeListBox an instance of a Vegetable
Vegetable selectedVege = (sender as ListBox).SelectedItem as Vegetable;
// Navigate to the new page
if (selectedVege != null)
{
//Navigate to the Carrot page sending the ID property of the selectedVege as a parameter query
NavigationService.Navigate(new Uri(string.Format("/Carrot.xaml?parameter={0}", selectedVege.ID), UriKind.Relative));
}
// Reset selected index to -1 (no selection)
VegeListBox.SelectedIndex = -1;
}
NOTE:In my ViewModel i had created a list of Vegetables(with each vegetable containing a list of Carrots) called VegeItems Then in the Carrot.xaml.cs page, you do this on the onNavigatedTo event:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
string parameter = this.NavigationContext.QueryString["parameter"];
Vegetable vegeItem = null;
int VegeId = -1;
if (int.TryParse(parameter, out VegeId))
{
Debug.WriteLine(VegeId);
vegeItem = App.ViewModel.VegeItems.FirstOrDefault(c => c.ID == VegeId);
DataContext = vegeItem;
}
}
Then in the Carrots.xaml, in the ItemSource of the listbox, i put binding to the Carrot(List of carrots) property of the Vegetable class like so:
<ListBox x:Name="FileList"
ItemsSource="{Binding Path=Carrot}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource PhoneTextLargeStyle}"
x:Name="ContentText"
Text="{Binding CarrotTypeName}"
TextWrapping="Wrap" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I have created a custom class ComboboxItem that takes Text/Value and a function to return a string.
public class ComboboxItem
{
public string Text { get; set; }
public object Value { get; set; }
public override string ToString()
{
return Text;
}
}
I populate the item and added it to a ComboBox.
ComboboxItem item = new ComboboxItem();
item.Text = result.CODEALIAS + " | " + result.DESCRIPTION;
item.Value = result.CODEALIAS;
comboBox.Items.Add(item);
and now the ComboBox shows both the codealias + description in each item. what I'm trying to do is just return the codealias in the content of the box once the user selects an item instead of the whole Text
I tried the following on SelectionChanged of ComboBox.
comboBox.Text = (comboBox.SelectedItem as ComboboxItem).Value.ToString();
And
comboBox.SelectedItem = (comboBox.SelectedItem as ComboboxItem).Value.ToString();
and the program just crashes with this error:
Object reference not set to an instance of an object.
I put a BreakPoint and it gets the value it just doesn't set it. Any ideas how to do this?
Item datacontext is set to a ViewSource since I need to load the initial value from a table. here's the XAML:
<ComboBox x:Name="comboBox" SelectionChanged="comboBox_SelectionChanged" DataContext="{StaticResource myTableViewSource}" Text="{Binding myField}" shell:WindowChrome.IsHitTestVisibleInChrome="True" Canvas.Left="58" Canvas.Top="192" Width="120"/>
Not exactly the smoothest UI experience, but you can swap the DisplayMemberPath between Text and Value when the ComboBox's DropDown is opened/closed. That just about meets your criteria.
XAML
<ComboBox x:Name="myComboBox" DropDownOpened="myComboBox_DropDownOpened" DropDownClosed="myComboBox_DropDownClosed" />
Codebehind
private void myComboBox_DropDownOpened(object sender, EventArgs e)
{
myComboBox.DisplayMemberPath = "Text";
}
private void myComboBox_DropDownClosed(object sender, EventArgs e)
{
myComboBox.DisplayMemberPath = "Value";
}
You should use WPF in a natural way, i.e. use DataTemplate to show what you want :
Code:
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var dataContext = new[]
{
new MyData
{
Text = "a happy smiley",
Value = ":)"
},
new MyData
{
Text = "a sad smiley",
Value = ":("
}
};
DataContext = dataContext;
}
}
internal class MyData
{
public string Text { get; set; }
public object Value { get; set; }
}
}
XAML :
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow"
Width="525"
Height="350">
<Grid>
<ComboBox ItemsSource="{Binding}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="wpfApplication1:MyData">
<TextBlock Text="{Binding Value}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
Result :