I have this combobox:
<ComboBox Grid.Column="1" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding Items, Mode=OneWay}" HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
and this is the code:
public class CustomComboBoxViewModel
{
private bool DiscardSelChanged { get; set; }
public ObservableCollection<string> Items { get; set; }
public string SelectedItem
{
get { return _selectedItem; }
set
{
if (!DiscardSelChanged)
_selectedItem = value;
bool old = DiscardSelChanged;
DiscardSelChanged = false;
if (!old)
SelectionChanged?.Invoke(_selectedItem);
}
}
public event Action<string> SelectionChanged;
public void AddItem(string item)
{
var v = Items.Where(x => x.Equals(item)).FirstOrDefault();
if (v != default(string))
{
SelectedItem = v;
}
else
{
DiscardSelChanged = true;
_selectedItem = item;
Items.Insert(0, item);
}
}
}
At startup I have only one item: Browse.... selecting it i can browse for a file and add its path to the ComboBox. AddItem method is called
If the selected file path doesn't exists in Items i add and select it (this is working).
If the selected file path exists in Items i want to automatically select it without adding it to the list again. this doesn't work and Browse... is the visualized item.
I already tried to use INotifyPropertyChanged.
I'm using .NET 4.6.2. Any ideas to get it working?
EDIT 4:barebone example
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
Items = new ObservableCollection<string>();
Items.Add(ASD);
}
private string ASD = #"BROWSE";
private string _selectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged(nameof(SelectedItem));
UploadFileSelection_SelectionChanged();
}
}
public ObservableCollection<string> Items { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private void AddItem(string item)
{
var v = Items.Where(x => x.Equals(item)).FirstOrDefault();
if (v != default(string))
SelectedItem = v;
else
{
Items.Add(item);
SelectedItem = item;
}
}
private void UploadFileSelection_SelectionChanged()
{
if (SelectedItem == ASD)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog()
{
DefaultExt = ".*",
Filter = "* Files (*.*)|*.*"
};
bool? result = dlg.ShowDialog();
if (result == true)
AddItem(dlg.FileName);
}
}
}
}
comboBox:
<ComboBox SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding Items}"/>
try to:
- select FILE_A.txt
- select FILE_B.txt
- select FILE_A.txt again
I tried your example. I fixed the re-entrancy problem (double browse dialog) with a flag:
private bool _browsing = false;
private void UploadFileSelection_SelectionChanged()
{
if (_browsing)
{
return;
}
if (SelectedItem == ASD)
{
try
{
_browsing = true;
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog()
{
DefaultExt = ".*",
Filter = "* Files (*.*)|*.*"
};
bool? result = dlg.ShowDialog();
if (result == true)
AddItem(dlg.FileName);
}
finally
{
_browsing = false;
}
}
}
It's caveman stuff but it works.
The real problem you have is that UploadFileSelection_SelectionChanged() is called, and updates SelectedItem before you exit the SelectedItem setter from the call that sets it to ASD.
So SelectedItem = v; in AddItem() has no effect on the combobox, because the combobox isn't responding to PropertyChanged right then.
This will fix that:
private void AddItem(string item)
{
var v = Items.FirstOrDefault(x => x.Equals(item));
if (v != default(string))
{
//SelectedItem = v;
Task.Run(() => SelectedItem = v);
}
else
{
Items.Add(item);
SelectedItem = item;
}
}
Now we're doing it later.
But note that the other branch does work, the one where item is newly added to the collection. You can also fake it out by removing item and adding it again:
private void AddItem(string item)
{
// Harmless, if it's not actually there.
Items.Remove(item);
Items.Add(item);
SelectedItem = item;
}
That looks weirder, but since it doesn't rely on thread timing, it's probably a better solution. On the other hand, this is "viewmodel" code whose details are driven by the peculiarities of the implementation of the ComboBox control. That's not a good idea.
This should be probably be done in the view (leaving aside that in this contrived example our view is our viewmodel).
You are setting _selectedItem without calling OnPropertyChanged() afterwards. That's why it is not working. If you want a clear code solution consider implementing the property with OnPropertyChanged() like this:
int _example;
public int Example
{
get
{
return _example;
}
set
{
_example = value;
OnPropertyChanged(nameof(Example);
}
}
Your code will be less error prone.
Do it as easy as possible:
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> Strings { get; set; }
public ICommand AddAnotherStringCommand { get; set; }
string _selectedItem;
public string SelectedItem
{
get
{
return _selectedItem;
}
set
{
_selectedItem = value;
OnPropertyChanged(nameof(this.SelectedItem));
}
}
public int counter { get; set; } = 1;
public ViewModel()
{
// RelayCommand from: https://stackoverflow.com/questions/22285866/why-relaycommand
this.AddAnotherStringCommand = new RelayCommand<object>(AddAnotherString);
this.Strings = new ObservableCollection<string>();
this.Strings.Add("First item");
}
private void AddAnotherString(object notUsed = null)
{
this.Strings.Add(counter.ToString());
counter++;
this.SelectedItem = counter.ToString();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Main Window:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Test"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel x:Name="ViewModel" />
</Window.DataContext>
<StackPanel>
<ComboBox ItemsSource="{Binding Strings}" SelectedItem="{Binding SelectedItem}"/>
<Button Content="Add another item" Command="{Binding AddAnotherStringCommand}" />
</StackPanel>
</Window>
In my case the value is changed every time, but you should be able to modify the code to fit your needs.
Make sure that you have a clear code structure and do not overcomplicate things.
If you want a more specific answer you should consider to present you whole code.
Related
I've stumbled upon the well-known problem with Listbox and focus. I'm setting ItemsSource from the viewmodel and at some point I need to reload them and set selection and focus to a specific item, say:
private readonly ObservableCollection<ItemViewModel> items;
private ItemViewModel selectedItem;
private void Process()
{
items.Clear();
for (int i = 0; i < 100; i++)
{
items.Add(new ItemViewModel(i));
}
var item = items.FirstOrDefault(i => i.Value == 25);
SelectedItem = item;
}
public ObservableCollection<ItemViewModel> Items { /* usual stuff */ }
public ItemViewModel SelectedItem { /* usual stuff */ }
Binding may look like:
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
After calling the method item gets selected, but does not receive focus.
I've read a lot on the Internet and on StackOverflow, but all answers I found involve manual filling of the listbox, not via binding from viewmodel. So the question is: how can I properly focus newly selected item in the presented scenario?
To add some context, I'm implementing a sidebar file browser:
I need keyboard navigation on the listbox below treeview.
Here is a solution that might work for you:
The control:
class FocusableListBox : ListBox
{
#region Dependency Proeprty
public static readonly DependencyProperty IsFocusedControlProperty = DependencyProperty.Register("IsFocusedControl", typeof(Boolean), typeof(FocusableListBox), new UIPropertyMetadata(false, OnIsFocusedChanged));
public Boolean IsFocusedControl
{
get { return (Boolean)GetValue(IsFocusedControlProperty); }
set { SetValue(IsFocusedControlProperty, value); }
}
public static void OnIsFocusedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
ListBox listBox = dependencyObject as ListBox;
listBox.Focus();
}
#endregion Dependency Proeprty
}
The ViewModel:
class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Boolean _IsFocused;
private String selectedItem;
public ObservableCollection<String> Items { get; private set; }
public String SelectedItem
{
get
{
return selectedItem;
}
set
{
selectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
public Boolean IsFocused
{
get { return _IsFocused; }
set
{
_IsFocused = value;
RaisePropertyChanged("IsFocused");
}
}
public ViewModel()
{
Items = new ObservableCollection<string>();
Process();
}
private void Process()
{
Items.Clear();
for (int i = 0; i < 100; i++)
{
Items.Add(i.ToString());
}
ChangeFocusedElement("2");
}
public void ChangeFocusedElement(string newElement)
{
var item = Items.FirstOrDefault(i => i == newElement);
IsFocused = false;
SelectedItem = item;
IsFocused = true;
}
private void RaisePropertyChanged(String propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
The XAML:
<local:FocusableListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}"
HorizontalAlignment="Left" VerticalAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Width="200"
IsFocusedControl="{Binding IsFocused, Mode=TwoWay}"/>
The update call:
_viewModel.ChangeFocusedElement("10");
I ended up with the following code in control's codebehind:
public void FixListboxFocus()
{
if (lbFiles.SelectedItem != null)
{
lbFiles.ScrollIntoView(lbFiles.SelectedItem);
lbFiles.UpdateLayout();
var item = lbFiles.ItemContainerGenerator.ContainerFromItem(viewModel.SelectedFile);
if (item != null && item is ListBoxItem listBoxItem && !listBoxItem.IsFocused)
listBoxItem.Focus();
}
}
This method is available for calling from within viewModel, which calls it every time it sets the selection:
var file = files.FirstOrDefault(f => f.Path.Equals(subfolderName, StringComparison.OrdinalIgnoreCase));
if (file != null)
SelectedFile = file;
else
SelectedFile = files.FirstOrDefault();
access.FixListboxFocus();
The access is view passed to ViewModel via interface (to keep separation between presentation and logic). The relevant XAML part looks like following:
<ListBox x:Name="lbFiles" ItemsSource="{Binding Files}" SelectedItem="{Binding SelectedFile}" />
I have a view model with a collection of items, each item has a collection of keys. I want to make DataGridComboBoxColumn to display a drop down list of the keys for each item. I have seen similar questions, but none of the answers helped me. When I run my application, all comboboxes are empty. Here is my xaml:
<Window
x:Class="TestDataGridCombobox.MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"/>
<DataGridComboBoxColumn ItemsSource="{Binding Path=Keys}" SelectedValueBinding="{Binding Path=SelectedKey}"/>
</DataGrid.Columns>
</DataGrid>
</Window>
And here is my view model:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace TestDataGridCombobox
{
public class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
Items.Add(new MyItem { Name = "Item1" });
Items.Add(new MyItem { Name = "Item2" });
Items.Add(new MyItem { Name = "Item3" });
}
private ObservableCollection<MyItem> items = new ObservableCollection<MyItem>();
public ObservableCollection<MyItem> Items
{
get { return items; }
set
{
if (items == value)
return;
items = value;
OnPropertyChanged("Items");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public class MyItem : INotifyPropertyChanged
{
public MyItem()
{
Keys.Add("Key1");
Keys.Add("Key2");
Keys.Add("Key3");
SelectedKey = "Key1";
}
private string name;
public string Name
{
get { return name; }
set
{
if (name == value)
return;
name = value;
OnPropertyChanged("Name");
}
}
private string selectedKey;
public string SelectedKey
{
get { return selectedKey; }
set
{
if (selectedKey == value)
return;
selectedKey = value;
OnPropertyChanged("SelectedKey");
}
}
private ObservableCollection<string> keys = new ObservableCollection<string>();
public ObservableCollection<string> Keys
{
get { return keys; }
set
{
if (keys == value)
return;
keys = value;
OnPropertyChanged("Keys");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}
And the view model is bound to window like this:
public partial class MyWindow : Window
{
public MyWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
I could use a templated column, but I'm interested in why this particular example doesn't work, are there any issues with the code? Or are there any limitations to DataGridComboBoxColumn?
Or are there any limitations to DataGridComboBoxColumn?
Yes, there are some limitations. You can find them by reading the Remarks section in the DataGridComboBoxColumn Class documentation:
To populate the drop-down list, first set the ItemsSource property for the ComboBox by using one of the following options:
A static resource. For more information, see StaticResource Markup Extension.
An x:Static code entity. For more information, see x:Static Markup Extension.
An inline collection of ComboBoxItem types.
There are a lot of workarounds to solve this issue, for example - as you wrote - you can use a DataGridTemplateColumn.
I hope it can help you.
I've been trying to create a user control, using this article as my start.
My end result would be supplying a collection and using the TextBox to filter the collection, displaying the filtered result in the ListView.
Problem is, it seems that my binding isn't working correctly. The collection isn't being passed through to the UserControl. Bear in mind, this is my first shot at creating a UserControl. Why would this happen?
SearchList.Xaml
<UserControl x:Class="CreatingControls.SearchList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CreatingControls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Loaded="SearchList_OnLoaded">
<StackPanel>
<TextBox Name="txtFilter"
TextChanged="txtFilter_TextChanged"
Margin="5" FontSize="20" />
<TextBox IsEnabled="False" Text="Search:"
FontSize="16" BorderThickness="0" />
<ListView Name="listView"
SelectedValue="{Binding Path=SelectedItem}"
DisplayMemberPath="Value"
BorderBrush="LightGray" Margin="5" />
</StackPanel>
SearchList.xaml.cs
public partial class SearchList : UserControl
{
#region AllItems
public List<Collection> AllItems
{
get { return (List<Collection>)GetValue(AllItemsProperty); }
set { SetValue(AllItemsProperty, value); }
}
public static readonly DependencyProperty AllItemsProperty =
DependencyProperty.Register("AllItems", typeof(List<Collection>),
typeof(SearchList), new PropertyMetadata(default(List<Collection>)));
#endregion
#region SelectedItem
public Collection SelectedItem
{
get { return (Collection)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(Collection),
typeof(SearchList), new PropertyMetadata(default(Collection)));
#endregion
public SearchList()
{
InitializeComponent();
listView.ItemsSource = AllItems;
if (listView.ItemsSource != null)
{
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(listView.ItemsSource);
view.Filter = ItemsFilter;
}
}
private void SearchList_OnLoaded(object sender, RoutedEventArgs e)
{
if (listView.ItemsSource == null)
return;
CollectionViewSource.GetDefaultView(listView.ItemsSource).Filter = ItemsFilter;
}
private void txtFilter_TextChanged(object sender, TextChangedEventArgs e)
{
if (listView.ItemsSource == null)
return;
CollectionViewSource.GetDefaultView(listView.ItemsSource).Refresh();
}
private bool ItemsFilter(object item)
{
if (listView.ItemsSource == null)
return false;
if (String.IsNullOrEmpty(txtFilter.Text))
return true;
var collectionItem = (Collection)item;
return (collectionItem.Value.StartsWith(txtFilter.Text, StringComparison.OrdinalIgnoreCase) || collectionItem.Value.StartsWith(txtFilter.Text, StringComparison.OrdinalIgnoreCase));
}
}
Collection.cs
public class Collection : INotifyPropertyChanged
{
private string _id;
public string Id
{
get { return _id; }
set { SetField(ref _id, value, "Id"); }
}
private string _value;
public string Value
{
get { return _value; }
set { SetField(ref _value, value, "Value"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
MainWindow.xaml (Window that calls the UserControl created)
<Window x:Class="CreatingControls.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uControls="clr-namespace:CreatingControls"
mc:Ignorable="d"
d:DataContext="Models."
Title="MainWindow" >
<Grid>
<uControls:SearchList x:Name="slist" />
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public static List<Collection> items { get; set; }
public User SelectedUser { get; set; }
public MainWindow()
{
InitializeComponent();
items = new List<Collection>();
items.Add(new Collection { Id = "1", Value = "A" });
items.Add(new Collection { Id = "2", Value = "B" });
items.Add(new Collection { Id = "3", Value = "C" });
items.Add(new Collection { Id = "4", Value = "D" });
items.Add(new Collection { Id = "5", Value = "E" });
items.Add(new Collection { Id = "6", Value = "F" });
items.Add(new Collection { Id = "7", Value = "G" });
items.Add(new Collection { Id = "8", Value = "H" });
slist.AllItems = items;
}
}
You are assigning
listView.ItemsSource = AllItems;
in the SearchList constructor. Later, in the MainWindow constructor, you do
slist.AllItems = items;
Now you seem to be under the impression that listView.ItemsSource magically holds a reference to the items collection of your MainWindow. That is not the case.
Instead of the direct assignment, use data binding. In the SearchList XAML, write this:
<ListView ItemsSource="{Binding AllItems,
RelativeSource={RelativeSource AncestorType=UserControl}}" .../>
and remove listView.ItemsSource = AllItems from the SearchList constructor.
I bind a class which derived from INotifyPropertyChange to a Datacontext.
after some interaction, a value will be calculated and output property will be updated.
My problem is that the result textbox didn't update at all.
public partial class setraSubWpfTolerance : UserControl
{
public setraFit objSource = new setraFit();
public setraSubWpfTolerance()
{
InitializeComponent();
this.DataContext = objSource;
}
}
And the class:
public class setraFit : INotifyPropertyChanged
{
private readonly CollectionView _BoreSystems;
public CollectionView BoreSystems
{
get { return _BoreSystems; }
}
private decimal? _MaxBoreDimension;
public decimal? MaxBoreDimension
{
get { return _MaxBoreDimension; }
set
{
if (_MaxBoreDimension == value) return;
_MaxBoreDimension = value;
onPropertyChanged("MaxBoreDimension");
}
}
private string _BoreSystem;
public string BoreSystem
{
get { return _BoreSystem; }
set
{
if (_BoreSystem == value) return;
_BoreSystem = value;
calcBoreDimension();
onPropertyChanged("BoreSystem");
}
}
public setraFit()
{
IList<string> listBore = setraStaticTolerance.getBoreList();
_BoreSystems = new CollectionView(listBore);
}
public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void calcBoreDimension()
{
_MaxBoreDimension = (decimal)100.035;
}
}
Last but not least the XAML
<UserControl x:Class="SetraSubForms.setraSubWpfTolerance"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="375">
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="194,10,0,0" Name="BoreSystemComboBox" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Path=BoreSystems}"
SelectedValue="{Binding Path=BoreSystem}"/>
<TextBox HorizontalAlignment="Left" Margin="194,67,0,37" Name="MaxDimBoreTextBox" Width="120" IsReadOnly="False"
Text="{Binding Path=MaxBoreDimension, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</Grid>
</UserControl>
I expected to receive the dummy value of 100.035 after changing the combobox but the textbox did not update. If i run step by step i can see the "MaxBoreDimension" property of setraFit is changed.
What did i do wrong?
Thanks in advance for your help
sittingDuck
Your method is updating the private value, not the Property:
private void calcBoreDimension()
{
_MaxBoreDimension = (decimal)100.035;
}
Change to
private void calcBoreDimension()
{
MaxBoreDimension = (decimal)100.035;
}
You're doing the same thing in the constructor, which is causing your calcBoreDimension method to not run:
public setraFit()
{
IList<string> listBore = setraStaticTolerance.getBoreList();
_BoreSystems = new CollectionView(listBore);
}
should be
public setraFit()
{
IList<string> listBore = setraStaticTolerance.getBoreList();
BoreSystems = new CollectionView(listBore); //this line!
}
When you create properties that point to private fields, you should almost never have to set the private field anywhere other than the property. This is why properties exist- so that whenever you get or set them, you will run the code in the get and set blocks instead of just retrieving the current value.
SOLVED!
The key is to initate the PropertyChanged event for the "MaxBoreDimension"
public decimal? NominalDimension
{
get { return _NominalDimension; }
set
{
if (_NominalDimension == value) return;
_NominalDimension = value;
calcBoreDimension();
onPropertyChanged("NominalDimension");
onPropertyChanged("MaxBoreDimension");
}
}
Thanks DLeh for the contribution.
I'm building application using the MVVM pattern. After clicking on one of the elements I want to see this element's details. I wrote this:
XAML
<phone:LongListSelector ItemsSource="{Binding Data}"
Margin="0,0,0,158"
SelectedItem="{Binding SelectedItem}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button>
<!-- Command="{Binding ShowDetailsAction}"-->
<Button.Template>
<ControlTemplate>
<TextBlock Text="{Binding Text}"></TextBlock>
</ControlTemplate>
</Button.Template>
</Button>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
ViewModel:
public IEnumerable SelectedItem
{
get { return _itemsControl; }
set
{
if (_itemsControl == value)
return;
_itemsControl = value;
// Test
_mss.ErrorNotification("fd");
}
}
I tried also using a command, which didn't work, too.
This was the command part:
public ICommand ShowDetailsCommand { get; private set; }
public ViewModel()
{
_loadDataCommand = new DelegateCommand(LoadDataAction);
SaveChangesCommand = new DelegateCommand(SaveChangesAction);
ShowDetailsCommand = new DelegateCommand(ShowDetailsAction);
}
private void ShowDetailsAction(object p)
{
_mss.ErrorNotification("bla bla");
}
EDIT
ViewModel
private IEnumerable _itemsControl;
public IEnumerable Data
{
get
{
return _itemsControl;
}
set
{
_itemsControl = value;
RaisePropertyChanged("Data");
}
}
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Model
public string Text { get; set; }
public DateTimeOffset Data { get; set; }
EDIT2
private MobileServiceCollection<ModelAzure, ModelAzure> _items;
private readonly IMobileServiceTable<ModelAzure> _todoTable = App.MobileService.GetTable<ModelAzure>();
private async void RefreshTodoItems()
{
try
{
_items = await _todoTable.ToCollectionAsync();
}
catch (MobileServiceInvalidOperationException e)
{
_mss.ErrorNotification(e.ToString());
}
Data = _items;
}
Your Data property looks like
private MobileServiceCollection<ModelAzure, ModelAzure> _itemsControl;
public MobileServiceCollection<ModelAzure, ModelAzure> Data
{
get
{
return _itemsControl;
}
set
{
_itemsControl = value;
RaisePropertyChanged("Data");
}
}
Edited
It seems the SelectedItem property from LongListSelector cannot be bound in WP8.
What you can do is either :
Use the derived and fixed custom LongListSelector provided in the link above instead of the default one, which looks like :
public class LongListSelector : Microsoft.Phone.Controls.LongListSelector
{
public LongListSelector()
{
SelectionChanged += LongListSelector_SelectionChanged;
}
void LongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItem = base.SelectedItem;
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
"SelectedItem",
typeof(object),
typeof(LongListSelector),
new PropertyMetadata(null, OnSelectedItemChanged)
);
private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = (LongListSelector)d;
selector.SelectedItem = e.NewValue;
}
public new object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
}
Register the SelectionChanged event from LongListSelector and call your ViewModel by yourself inside the associated handler/callback :
in your view :
<phone:LongListSelector x:Name="YourLongListSelectorName"
ItemsSource="{Binding Data}"
Margin="0,0,0,158"
SelectionChanged="OnSelectedItemChanged">
in your code behind :
private void OnSelectedItemChanged(object sender, SelectionChangedEventArgs selectionChangedEventArgs e)
{
((YourViewModel)this.DataContext).NewSelectedItemMethodOrWhateverYouWant((ModelAzure)this.YourLongListSelectorName.SelectedItem);
//or
((YourViewModel)this.DataContext).SelectedItem = (ModelAzure)this.YourLongListSelectorName.SelectedItem;
}
Finally your Button command wasn't properly working, because when you use a DataTemplate, the ambiant DataContext is the item itself. Which means that it was looking for your Command into your Model instance, not into your ViewModel instance.
Hope this helps
In your ViewModel, you have:
public IEnumerable SelectedItem
{
get { return _itemsControl; }
set
{
if (_itemsControl == value)
return;
_itemsControl = value;
// Test
_mss.ErrorNotification("fd");
}
}
Why is your SelectItem an IEnumerable? Should it not be of type "Model"? Your list is bound to "Data" which should be ObservableList, not IEnumerable. It will provide it's own change notification, so you don't need to.
The list will set the SelectedItem when it gets selected, but if the type is wrong, it won't get set.
Greg