ListView Not updating if a delete an item - xaml

I have a listView which is set to Mode=TwoWay, which i thought was suppose to refresh the views, if the underlying data changed.
However, while the item is deleted correctly, the item still remains on the list, until I exit and return to the page.
Xaml:
<ListView ItemsSource="{Binding Path=items, Mode=TwoWay}" >
<DataTemplate>
...
<Button x:Name="btn_delete_item" Click="btn_delete_item_Click" >
</Button>
Behind code:
private void btn_delete_item_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
itemType item = button.DataContext as itemType;
items.Remove(item);
}

In order to fully support data binding, your Items collection must notify about changes, i.e. whether items are added, removed, replaced or moved. This notification is done by implementing the INotifyCollectionChanged interface. The framework's List<T> type does not implement this interface, but ObservableCollection<T> does.
So you could simply change the type of your Items property:
public ObservableCollection<ItemType> Items { get; set; }

Related

Mutually exclusive selection of two listviews

I've implemented a UWP SplitView similar to the one made by Diederik Krols. I prefer the approach of using a ListView over using RadioButtons as shown by Jerry Nixon's implementation of the SplitView.
However, I have a problem when I add secondary commands at the bottom of the SplitView, which Diederik doesn't do. These secondary commands are implemented by another ListView bound to a collection of Commands. So I have TWO ListViews that should only allow ONE item to be selected among them at a time.
I've tried two things:
I've bound the SelectedItem property of both ListViews to the same object. The idea was that maybe ListView doesn't display a selection if SelectedItem is not in the list bound to ItemsSource. Sadly, it simply goes on displaying the last selected item.
I've bound each ListView's SelectedItem to its own property. When one of the ListViews' item is selected, the SelectedItem of the other property is set to 'null' by the ViewModel. This produces the same result as in 1.
Any ideas on how to solve this problem?
I had the same problem.
I have a fix, but I'm not that proud of it ;) it's a dirty hack and I'm hoping other solutions will present itself so I can change it too.
But here it is:
First the listviews hook up to the SelectionChanged event even though we also bind the selected item to the viewmodel ( full code shown here https://github.com/AppCreativity/Kliva/blob/master/src/Kliva/Controls/SidePaneControl.xaml )
<ListView x:Name="TopMenu"
SelectionChanged="OnTopMenuSelectionChanged"
Background="Transparent"
ItemsSource="{x:Bind ViewModel.TopMenuItems}"
ItemTemplateSelector="{StaticResource MenuItemTemplateSelector}"
ItemContainerStyle="{StaticResource MenuItemContainerStyle}"
SelectedItem="{x:Bind ViewModel.SelectedTopMenuItem, Mode=TwoWay, Converter={StaticResource XBindItemCastingConverter}}"
Grid.Row="0" />
In that SelectionChanged, we'll deselect the 'other' listviews selection! ( full code shown here https://github.com/AppCreativity/Kliva/blob/master/src/Kliva/Controls/SidePaneControl.xaml.cs )
Note that we need to keep track that we are already in a deselecting process otherwise we'll end up with an endless loop. This is done with the _listViewChanging field.
private void OnBottomMenuSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!_listViewChanging)
{
_listViewChanging = true;
TopMenu.SelectedIndex = -1;
_listViewChanging = false;
}
}
Last thing to do is making sure we handle the selection and clear it again in the viewmodel for next itteration ( full code shown here https://github.com/AppCreativity/Kliva/blob/master/src/Kliva/ViewModels/SidePaneViewModel.cs )
public MenuItem SelectedBottomMenuItem
{
get { return _selectedBottomMenuItem; }
set
{
if (Set(() => SelectedBottomMenuItem, ref _selectedBottomMenuItem, value))
{
if (value != null)
{
if (string.IsNullOrEmpty(SelectedBottomMenuItem.Title))
HamburgerCommand.Execute(null);
if (SelectedBottomMenuItem.Title.Equals("settings", StringComparison.OrdinalIgnoreCase))
SettingsCommand.Execute(null);
SelectedBottomMenuItem = null;
}
}
}
}

Conditionally fire ItemClick

I have a ListView populated with items that should perform actions when tapped, but only when tapped on certain areas:
XAML:
<ListView
SelectionMode="None"
IsItemClickEnabled="True"
ItemClick="MyListView_ItemClick">
<ListView.ItemTemplate>
<DataTemplate>
<custom:MyListViewItemControl Tapped="ListViewItem_Tapped"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code-behind:
private void MyListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.OriginalSource is Image) return; // Ignore if clicked on Image
if (this.ItemClickHandler != null)
{
this.ItemClickHandler(this, e);
}
}
In this code, the e.OriginalSource is always the ListView, so the approach of checking the type of the OriginalSource does not work.
I can also use the Tapped event on individual ListView items, but this changes the type of events received (to TappedRoutedEventArgs), so I'll have to change my handler signature all the way up the stack, which I'd like to avoid if possible. (I can't just make a new ItemClickEventArgs because there's no way to set its values that I can see, and it's a sealed class so I can't subclass it.)

Notify ListBox and ListBox Item for CRUD from Background Thread

I'm working with LINQ/SQL within my WP8 app to manage a collection of items that are shown within a ListBox.
Sometimes one these objects is changed by a long runned operation using another DataContext.
These changes could be one of the CRUD operations, so objects can be created in the background, too.
In iOS there is the NSFetchedResultsController for it or i can use a NSNotification - i'm searching for something similar.
update some code for an updating case
I'm fetching my items like this:
void loadData() {
var items = from r in itemDB.items orderby r.UpdatedAt descending select r;
var Items = new ObservableCollection<Item>(items);
lstData.ItemsSource = Items;
}
my listBox looks like this
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="10" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding LocalState}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and my background code looks like this:
static public void UploadAll() {
ThreadPool.SetMaxThreads(1,1);
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
}
static void ThreadProc(Object stateInfo)
{
ItemsContext itemsDB = new ItemsContext(ItemsContext.DBConnectionString);
var notUploadedItems = (from i in itemsDB.items
where !i.LocalState.Equals("server")
select i);
var Items = new ObservableCollection<Item>(notUploadedItems);
foreach (Item a in Items)
{
a.Save(() =>
{
Deployment.Current.Dispatcher.BeginInvoke(delegate
{
a.LocalState = "server";
itemsDB.SubmitChanges();
});
//NOW THE LISTBOX SHOULD BE UPDATED
}, (err) =>
{
});
}
}
at the //NOW THE LISTBOX SHOULD BE UPDATEDcomment, the corresponding listbox template should be updated
add/delete cases are similar.
cheers.
To be straight to the point, creating new instance of the ObservableCollection every time your data changes then re-assigning it to the ItemSource, is not only disregarding core purpose of the ObservableCollection, it is also breaking/misusing the concept of Data
Binding.
//this is the line I am talking about:
var Items = new ObservableCollection<Item>(notUploadedItems);
Instead, create the ObserbaleCollection once -- make it a property:
private readonly ObservableCollection<Item> _items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items {get {return _items;}
For the updates use collection's '.Add()' or 'Remove()' methods
You're correct that you must be on the UI Main thread to do the updates:
DoItOnUIThread(() =>Items.Add(newItem));
//here's a sample of method that would take any action and execute it on UI thread..
private void DoItOnUIThread(Action action)
{
if (_dispatchService == null)
_dispatchService = ServiceLocator.Current.GetInstance<IDispatchService>();
if (_dispatchService.CheckAccess())
action.Invoke ();
else
_dispatchService.Invoke(action);
}
Let the observable collection do its job, which is automatically update the UI through Binding with new items and their property changes. If all that is hooked up/done properly your listbox shouldn't need to be updated in cs code at all.

How to disable right-button selection on List View Item (ListViewItem)?

I want to prevent the ability to deselect a list view item if it is already selected. Therefore how do I prevent right mouse click ability to deselect an item?
I have prevented the ability to deselect via swiping by using IsSwipeEnabled="False" on the List View. I didn't require swipe ability on the list view.
I'm happy to completely prevent right mouse click on the list view items if needed.
If I am reading your question correctly, it sounds like you want the ability for the user to select items, but to not be able to de-select. If that is the case, it seems like a strange requirement - it goes against normal UI convention and does something that the user is not expecting.
Having said that, you can do so by handling the SelectionChanged event in the ListView.
When the event is triggered, it gives you a list of removed (de-selected) items. You then just need to add those items back into the ListView's selected items list:
private void itemListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var item in e.RemovedItems)
{
itemListView.SelectedItems.Add(item);
}
}
Note that if you use the above code, you do not have to handle any swipe or mouse events.
Edit - Per OP's comment, the requirement is slightly different than what I thought:
I want the selected item to deselect if a different item is selected. however what I dont want is an already selected item to be (manually) deselected
Assuming that you have a single select ListView, you can still use the SelectionChanged event and the SelectionChangedEventArgs to do what you are asking for:
private void itemListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count > 0 && e.AddedItems.Count == 0)
{
var removed = e.RemovedItems[0];
itemListView.SelectedItem = removed;
}
}
I have found a simple solution on the following forum.
You simply add a RightTapped event to the ListView DataTemplate content.
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<ContentPresenter RightTapped="daves_RightTapped" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And then in the code behind:
private void daves_RightTapped(object sender, Windows.UI.Xaml.Input.RightTappedRoutedEventArgs e)
{
e.Handled = true;
}
This works fine on an outlook style ListView.

Blend 4 Passing Data Context with Navigate To

I have a list of customers with various pieces of information. I have a list box with their names. When I select an entry I see more information about the customer on the screen. I want to "Navigate To" another screen when clicking on the user's name with more of their information. I can't figure out how to pass information about the entry to the next screen to accomplish this.
Here is the list box that the user chooses from to begin with.
<ListBox x:Name="scheduleListBox"
ItemTemplate="{DynamicResource ItemTemplate}"
ItemsSource="{Binding Collection}"
Margin="8,8,8,0"
Style="{DynamicResource ListBox-Sketch}"
Height="154"
VerticalAlignment="Top"/>
Here is the TextBlock that could be clicked to go to the other screen. It is changed based on what the user selected from the ListBox.
<TextBlock Text="{Binding Customer}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="150" Margin="104,0,0,0"
Style="{DynamicResource BasicTextBlock-Sketch}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<pi:NavigateToScreenAction TargetScreen="V02Screens.Customer_Status"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
I'm kind of hoping that there is something I can do in Expression Blend 4 or in the XAML.
In Windows 8, you can pass the entire object to the receiving page.
Like this:
// main page
private void ListBox_SelectionChanged_1
(object sender, SelectionChangedEventArgs e)
{
var _Item = (sender as ListBox).SelectedItem;
Frame.Navigate(typeof(MainPage), _Item);
}
// detail page
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.DataContext = e.Parameter;
}
In WPF & SL, you can save reference to the SelectedItem in your View Model.
// main page
private void ListBox_SelectionChanged_1
(object sender, SelectionChangedEventArgs e)
{
var _Item = (sender as ListBox).SelectedItem;
MyModel.SelectedItem = _Item;
// TODO: navigate
}
// detail page
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.DataContext = MyModel.SelectedItem;
}
I hope this helps.
In WPF you can supply an object to the Navigate command which contains anything you want, including whatever data you might want to show on the next page. Then on the target page (the one you navigate to), you have to handle the load completed event.
In your first page you might navigate with...
this.NavigationService.Navigate( somePage, someContainerObject );
Then you might retrieve it on somePage with...
// Don't forget to subscribe to the event!
this.NavigationService.LoadCompleted += new LoadCompletedEventHandler(container_LoadCompleted);
...
void container_LoadCOmpleted( object sender, NavigationEventArgs e)
{
if( e.ExtraData != null )
// cast e.ExtraData and use it
}