I have a Listview in my application for which i need to bind more than 1000 items. while fetching the data i am able to display a message to the user saying "fetching items....". once fetching the data completes "fetching items.." message got disappeared and after some time my data is being displayed in listview.
basically message is disappearing during databinding time. so my question is, how can i display "fetching items..." message until my listview completes binding of all the items ?
can anyone please suggest me some ideas on how to implement this.
thanks in advance.
You can achieve this by putting your ListView and the TextBlock in a Grid. And switch between them using the visibility. Use a Task to fetch your Items and then show your ListView when the Task is finished.
Xaml:
<Grid>
<TextBlock Text="yourText" Visibility="{Binding TextBlockVisibility}" HorizontalAlignment="Center"/>
<ListView ItemsSource="{Binding yourItems}" Visibility="{Binding ListViewVisibility}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
and your ViewModel:
private Visibility _textBlockVisibility;
public Visibility TextBlockVisibility
{
get { return _textBlockVisibility; }
set
{
_textBlockVisibility = value;
RaisePropertyChanged();
}
}
private Visibility _listViewVisibility;
public Visibility ListViewVisibility
{
get { return _listViewVisibility; }
set
{
_listViewVisibility = value;
RaisePropertyChanged();
}
}
private async void StartFetchItems()
{
ShowTextBlock();
await Task.Factory.StartNew(FetchItemsAsync).ContinueWith(task2=>ShowListView());
}
private async Task FetchItemsAsync()
{
....fetchyourItems
}
private void ShowTextBlock()
{
TextBlockVisibility=Visibility.Visible;
ListViewVisibility=Visibility.Collapsed;
}
private void ShowListView()
{
ListViewVisibility=Visibility.Visible;
TextBlockVisibility=Visibility.Collapsed;
}
Related
I have a ListView that I would like to populate in XAML. I'm using a custom DataTemplate to make each ListViewItem added contain a Label and a TextBlock.
The problem is I need to dynamically populate the text of the TextBlock of each ListViewItem with data from a settings property, and I don't know how to create this binding.
Right now I am populating the ListView with an XmlDataProvider, but I can't (or at least can't figure out how to) bind values to the xml data. (I'm not stuck using this method of data population, it's just what I was originally doing when I ran into this problem.)
Basically I need something as follows:
The user enters some data into a text box. That data is saved to user settings. When that happens, the corresponding TextBlock of the ListViewItem in the ListView is updated with the user setting data.
Normally I would bind a TextBlock's text to a user setting as follows:
Text="{Binding Source={x:Static properties:Settings.Default},Path=User_Data_1}"
But how do I do this when the text of the TextBlock is defined in the DataTemplate?
My DataTemplate and XmlDataProvider:
<DataTemplate x:Key="listViewTemplate">
<StackPanel Orientation="Vertical">
<Label x:Name="lblName" Content="{Binding XPath=name}"/>
<TextBlock x:Name="tbValue" Text="{Binding XPath=value}"/>
</StackPanel>
</DataTemplate>
<XmlDataProvider x:Key="PagesData" XPath="Pages">
<x:XData>
<Pages xmlns="">
<page id="page01">
<name>Text file:</name>
<value></value>
<source>Pages/Page_CreateFiles1.xaml</source>
</page>
<page id="page02">
<name>Xml file:</name>
<value></value>
<source>Pages/Page_CreateFiles2.xaml</source>
</page>
<page id="page03">
<name>Memory object database:</name>
<value></value>
<source>Pages/Page_CreateFiles3.xaml</source>
</page>
<page id="page04">
<name>Output database:</name>
<value></value>
<source>Pages/Page_CreateDB.xaml</source>
</page>
</Pages>
</x:XData>
</XmlDataProvider>
My ListView
<ListView x:Name="lvNavigation"
ItemTemplate="{DynamicResource listViewTemplate}"
ItemsSource="{Binding Source={StaticResource PagesData}, XPath=page}"/>
Create a view model with a collection of items
public class Item
{
public string Name { get; set; }
public string Value { get; set; }
}
public class ViewModel
{
public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>();
}
and set the MainWindow's DataContext to an instance of the view model class
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
DataContext = vm;
vm.Items.Add(new Item { Name = "Name 1", Value = "Value 1" });
vm.Items.Add(new Item { Name = "Name 2", Value = "Value 2" });
}
Bind to it like this:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Value}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Clemmens answer is right, but I just wanted to put it out there that I basically did his approach with a slight modification. I used events to trigger the data change in the listview.
I think it is my own fault though, because I didn't explain my problem well enough. First of all, I wanted to do everything from xaml and I don't think that was possible. Second, I failed to mention that I was using pages in a frame, where the data was coming from the pages and the listview was in my main window that contained the frame. So that's why I ended up using events to communicate between the page and the main window.
So in my main window I've defined my observable collection:
ObservableCollection<NavItem> NavItems = new ObservableCollection<NavItem>();
public MainWindow()
{
InitializeComponent();
NavItems.Add(new NavItem { Name = "Text file:", Value = "", Source = "Pages/Page_CreateFiles.xaml" });
NavItems.Add(new NavItem { Name = "Xml file:", Value = "", Source = "Pages/Page_CreateFiles.xaml" });
NavItems.Add(new NavItem { Name = "Memory object db:", Value = "", Source = "Pages/Page_CreateFiles.xaml" });
NavItems.Add(new NavItem { Name = "Output database:", Value = "", Source = "Pages/Page_CreateDB.xaml" });
lvNavigation.ItemsSource = NavItems;
...
}
"NavItem" is a class that is subscribed to INotifyPropertyChanged. Posting that code will just be a lot, so check out how to do that here: INotifyPropertyChanged
Then in each page I set up an event that I call with the data to send:
public static event EventHandler<NavUpdateMessage> UpdateMessage;
private void OnUpdateMessage(int id, string message)
{
NavUpdateMessage navUpdateMessage = new NavUpdateMessage();
navUpdateMessage.Id = id;
navUpdateMessage.Message = message;
var e = UpdateMessage;
if (e != null)
e(this, navUpdateMessage);
}
With the main window subscribed to that event:
public MainWindow()
{
...
Pages.Page_CreateFiles.UpdateMessage += Pages_UpdateMessage;
Pages.Page_CreateDB.UpdateMessage += Pages_UpdateMessage;
}
private void Pages_UpdateMessage(object sender, NavUpdateMessage e)
{
Dispatcher.BeginInvoke(new Action(() =>
{
NavItems[e.Id].Value = e.Message;
}));
}
I'm sure there's a better, more simple approach to this, but this is what I could figure out. And even though I'm sure no one will see this because this question definitely did not get any traction, please feel free to suggest a better solution so at least I can learn.
I have a ListView which I would like te re-evaluate its SelectedItem once it receives a new ItemSource. The goal of this is to 'remember' if the user already selected an item in the ListView.
XAML:
<ListView
x:Name="_matchingTvShowsFromOnlineDatabaseListView"
Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="3"
ItemsSource="{Binding AvailableMatchingTvShows}"
SelectedItem="{Binding AcceptedMatchingTvShow, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The SelectedItem is also bound to a property on my VM.
The VM:
public IWebApiTvShow AcceptedMatchingTvShow
{
get
{
IWebApiTvShow acceptedTvShow = null;
if (FoundTvShows.Count > 0)
{
var tvShowName = FoundTvShows[CurrentTvShow];
acceptedTvShow = AvailableTvShowMatches[tvShowName].FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
}
return acceptedTvShow;
}
set
{
if (value != null)
{
var tvShowName = FoundTvShows[CurrentTvShow];
var currentlyAcceptedTvShow =
AvailableTvShowMatches[tvShowName].FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
if (currentlyAcceptedTvShow != null)
{
currentlyAcceptedTvShow.Accepted = false;
}
value.Accepted = true;
}
OnPropertyChanged();
}
}
I made a screen shot of the application I am building, which hopefully makes clear what I am trying to achieve.
The idea would be that when the user is navigating through the TV Shows, the application would remember the associated TV Show.
Currently, when I associate a TV Show, and navigate to the next TV Show and back again, nothing is selected (the getter of the property AcceptedMatchingTvShow is not executed after setting the new ItemSource)
UPDATE:
Added the code for AvailableMatchingTvShows
private ObservableCollection<IWebApiTvShow> _availableMatchingTvShows;
public ObservableCollection<IWebApiTvShow> AvailableMatchingTvShows
{
get { return _availableMatchingTvShows; }
set
{
_availableMatchingTvShows = value;
OnPropertyChanged("AcceptedMatchingTvShow");
}
}
Without seeing all of your ViewModel, I'm guessing if you raise PropertyChanged("AcceptedMatchingTvShow") when the ItemsSource binding changes that would update the SelectedItem binding.
I am new to MVVM, and also fairly new to WPF. As a matter of fact I started programming just a few months ago. MVVM is really dng my head in with the binding concept, and I have been trying for days now to just simply make an application that allows you to select an item from a listbx, and when you click on the add button the selected item should be saved in a new list. The second listbox displays the latest items added, and you can select an item and delete it by using another button. ususally I would go for the click event and decorate my codebehind with pretty little methods, but I really want to learn how to do all this by using bindings and no codebehind.
I would be extremly happy for any help, and please remember that I am new to this and I really want to keep it as simple as possible :)
with kind regards Daniela
<WrapPanel HorizontalAlignment="Center" Margin=" 10">
<ListBox x:Name="Firstbox"
Width="100"
ItemsSource="{Binding FoodList}"
DisplayMemberPath="Name" >
</ListBox>
<Button Margin="10 >Select</Button>
<ListBox Width="100"></ListBox>
private List _foodList;
public List<FoodItem> FoodList
{
get { return _foodList; }
set { _foodList = value; }
}
private List<FoodItem> _newFoodList;
public List<FoodItem> NewFoodList
{
get { return _newFoodList; }
set { _newFoodList = value; }
}
public MainViewModel()
{
InitializeCommands();
GetFood();
}
private void GetFood()
{
FoodList = new List<FoodItem>()
{
new FoodItem() {Name="Applepie"},
new FoodItem() {Name="Scones"}
};
}
first, you need to replace the Lists with ObservableCollections, so that the UI can detect when new items are added.
Add a SelectedItem property to your ViewModel:
private FoodItem _selectedItem;
public FoodItem SelectedItem
{
get { return _selectedItem;}
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
bind the SelectedItem property of the 1st ListBox to this property:
<ListBox Width=" 100" x:Name="Firstbox"
ItemsSource="{Binding FoodList}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedItem}" />
bind your 2nd ListBox to the NewFoodList property
create a command in your ViewModel:
private DelegateCommand _addItemCommand;
public ICommand AddItemCommand
{
get
{
if (_addItemCommand == null)
{
_addItemCommand = new DelegateCommand(AddItem);
}
return _addItemCommand;
}
}
void AddItem()
{
if (SelectedItem != null)
NewFoodList.Add(SelectedItem);
}
And finally, bind the button's Command property to the AddItemCommand property:
<Button Margin="10" Command="{Binding AddItemCommand}" >Select</Button>
When i selected the values in combo box,i have to hide another control. I have write the code as shown in below. Please correct me where i made mistake in that.
View Code:
<ComboBox x:Name="cboShowRuleWhere" Height="20" Width="200" ItemsSource="{Binding Source={StaticResource listedView}, Path=FilterRules}" DisplayMemberPath="RuleName" SelectedValuePath="RuleId" SelectedValue="{Binding Source={StaticResource listedView}, Path=SelectedRuleName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" ></ComboBox>
<ComboBox Height="21" HorizontalAlignment="Left" Margin="6,4,0,0" x:Name="cboRuleCondtion" Visibility="{Binding Path=IsButtonVisible,Converter={StaticResource BoolToVisible}}" VerticalAlignment="Top" Width="212" />
ViewModel Code:
private DataTable m_selectedRuleName;
public DataTable SelectedRuleName
{
get
{
return m_selectedRuleName;
}
set
{
m_selectedRuleName = value;
base.RaisePropertyChangedEvent("SelectedRuleName");
}
}
private bool _IsButtonVisible;
public bool IsButtonVisible
{
get { return _IsButtonVisible; }
set
{
_IsButtonVisible = value;
base.RaisePropertyChangedEvent("IsButtonVisible");
}
}
Where i have to correct? Please help me asap. Thanks in advance..
There's not a whole lot to go on here. For instance, where are you setting IsButtonvisible based on your rule criteria? Here are some ideas:
1) Don't create a backing field for IsButtonVisible. Instead, have it return the correct analysis.
public bool IsButtonVisible { get { return SelectedRuleName == "IsVisibleRule"; } }
2) You can fire the Notify Propery Changed event from anywhere. In this case, you want the IsButtonVisible binding to be reevaluated every time the SelectedRuleName changes:
private DataTable m_selectedRuleName;
public DataTable SelectedRuleName
{
get
{
return m_selectedRuleName;
}
set
{
m_selectedRuleName = value;
base.RaisePropertyChangedEvent("SelectedRuleName");
base.RaisePropertyChangedEvent("IsButtonVisible");
}
}
3) Is the SelectedRuleName really a DataTable? That would seem odd to me because it indicates multiple rows. It would be a longer post, but I would avoid DataTable altogether and change the ComboBox item source to an ObservableCollection. The "SelectedRuleName" would be of type T (not DataTable).
4) Along the same lines, I have found much greater success using SelectedItem instead of SelectedValue.
I hope some of this helps.
I am creating a WPF window with a DataGrid, and I want to show the blank "new item" row at the bottom of the grid that allows me to add a new item to the grid. For some reason, the blank row is not shown on the grid on my window. Here is the markup I used to create the DataGrid:
<toolkit:DataGrid x:Name="ProjectTasksDataGrid"
DockPanel.Dock="Top"
Style="{DynamicResource {x:Static res:SharedResources.FsBlueGridKey}}"
AutoGenerateColumns="False"
ItemsSource="{Binding SelectedProject.Tasks}"
RowHeaderWidth="0"
MouseMove="OnStartDrag"
DragEnter="OnCheckDropTarget"
DragOver="OnCheckDropTarget"
DragLeave="OnCheckDropTarget"
Drop="OnDrop"
InitializingNewItem="ProjectTasksDataGrid_InitializingNewItem">
<toolkit:DataGrid.Columns>
<toolkit:DataGridCheckBoxColumn HeaderTemplate="{DynamicResource {x:Static res:SharedResources.CheckmarkHeaderKey}}" Width="25" Binding="{Binding Completed}" IsReadOnly="false"/>
<toolkit:DataGridTextColumn Header="Days" Width="75" Binding="{Binding NumDays}" IsReadOnly="false"/>
<toolkit:DataGridTextColumn Header="Due Date" Width="75" Binding="{Binding DueDate, Converter={StaticResource standardDateConverter}}" IsReadOnly="false"/>
<toolkit:DataGridTextColumn Header="Description" Width="*" Binding="{Binding Description}" IsReadOnly="false"/>
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
I can't figure out why the blank row isn't showing. I have tried the obvious stuff (IsReadOnly="false", CanUserAddRows="True"), with no luck. Any idea why the blank row is disabled? Thanks for your help.
You must also have to have a default constructor on the type in the collection.
Finally got back to this one. I am not going to change the accepted answer (green checkmark), but here is the cause of the problem:
My View Model wraps domain classes to provide infrastructure needed by WPF. I wrote a CodeProject article on the wrap method I use, which includes a collection class that has two type parameters:
VmCollection<VM, DM>
where DM is a wrapped domain class, and DM is the WPF class that wraps it.
It truns out that, for some weird reason, having the second type parameter in the collection class causes the WPF DataGrid to become uneditable. The fix is to eliminate the second type parameter.
Can't say why this works, only that it does. Hope it helps somebody else down the road.
Vincent Sibal posted an article describing what is required for adding new rows to a DataGrid. There are quite a few possibilities, and most of this depends on the type of collection you're using for SelectedProject.Tasks.
I would recommend making sure that "Tasks" is not a read only collection, and that it supports one of the required interfaces (mentioned in the previous link) to allow new items to be added correctly with DataGrid.
In my opinion this is a bug in the DataGrid. Mike Blandford's link helped me to finally realize what the problem is: The DataGrid does not recognize the type of the rows until it has a real object bound. The edit row does not appear b/c the data grid doesn't know the column types. You would think that binding a strongly typed collection would work, but it does not.
To expand upon Mike Blandford's answer, you must first assign the empty collection and then add and remove a row. For example,
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// data binding
dataGridUsers.ItemsSource = GetMembershipUsers();
EntRefUserDataSet.EntRefUserDataTable dt = (EntRefUserDataSet.EntRefUserDataTable)dataGridUsers.ItemsSource;
// hack to force edit row to appear for empty collections
if (dt.Rows.Count == 0)
{
dt.AddEntRefUserRow("", "", false, false);
dt.Rows[0].Delete();
}
}
Add an empty item to your ItemsSource and then remove it. You may have to set CanUserAddRows back to true after doing this. I read this solution here: (Posts by Jarrey and Rick Roen)
I had this problem when I set the ItemsSource to a DataTable's DefaultView and the view was empty. The columns were defined though so it should have been able to get them. Heh.
This happned to me , i forgot to new up the instance and it was nightmare for me . once i created an instance of the collection in onviewloaded it was solved.
`observablecollection<T> _newvariable = new observablecollection<T>();`
this solved my problem. hope it may help others
For me the best way to implement editable asynchronous DataGrid looks like that:
View Model:
public class UserTextMainViewModel : ViewModelBase
{
private bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
this._isBusy = value;
OnPropertyChanged();
}
}
private bool _isSearchActive;
private bool _isLoading;
private string _searchInput;
public string SearchInput
{
get { return _searchInput; }
set
{
_searchInput = value;
OnPropertyChanged();
_isSearchActive = !string.IsNullOrEmpty(value);
ApplySearch();
}
}
private ListCollectionView _translationsView;
public ListCollectionView TranslationsView
{
get
{
if (_translationsView == null)
{
OnRefreshRequired();
}
return _translationsView;
}
set
{
_translationsView = value;
OnPropertyChanged();
}
}
private void ApplySearch()
{
var view = TranslationsView;
if (view == null) return;
if (!_isSearchActive)
{
view.Filter = null;
}
else if (view.Filter == null)
{
view.Filter = FilterUserText;
}
else
{
view.Refresh();
}
}
private bool FilterUserText(object o)
{
if (!_isSearchActive) return true;
var item = (UserTextViewModel)o;
return item.Key.Contains(_searchInput, StringComparison.InvariantCultureIgnoreCase) ||
item.Value.Contains(_searchInput, StringComparison.InvariantCultureIgnoreCase);
}
private ICommand _clearSearchCommand;
public ICommand ClearSearchCommand
{
get
{
return _clearSearchCommand ??
(_clearSearchCommand =
new DelegateCommand((param) =>
{
this.SearchInput = string.Empty;
}, (p) => !string.IsNullOrEmpty(this.SearchInput)));
}
}
private async void OnRefreshRequired()
{
if (_isLoading) return;
_isLoading = true;
IsBusy = true;
try
{
var result = await LoadDefinitions();
TranslationsView = new ListCollectionView(result);
}
catch (Exception ex)
{
//ex.HandleError();//TODO: Needs to create properly error handling
}
_isLoading = false;
IsBusy = false;
}
private async Task<IList> LoadDefinitions()
{
var translatioViewModels = await Task.Run(() => TranslationRepository.Instance.AllTranslationsCache
.Select(model => new UserTextViewModel(model)).ToList());
return translatioViewModels;
}
}
XAML:
<UserControl x:Class="UCM.WFDesigner.Views.UserTextMainView"
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:model="clr-namespace:Cellebrite.Diagnostics.Model.Entities;assembly=Cellebrite.Diagnostics.Model"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:converters1="clr-namespace:UCM.Infra.Converters;assembly=UCM.Infra"
xmlns:core="clr-namespace:UCM.WFDesigner.Core"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<DockPanel>
<StackPanel Orientation="Horizontal"
DockPanel.Dock="Top"
HorizontalAlignment="Left">
<DockPanel>
<TextBlock Text="Search:"
DockPanel.Dock="Left"
VerticalAlignment="Center"
FontWeight="Bold"
Margin="0,0,5,0" />
<Button Style="{StaticResource StyleButtonDeleteCommon}"
Height="20"
Width="20"
DockPanel.Dock="Right"
ToolTip="Clear Filter"
Command="{Binding ClearSearchCommand}" />
<TextBox Text="{Binding SearchInput, UpdateSourceTrigger=PropertyChanged}"
Width="500"
VerticalContentAlignment="Center"
Margin="0,0,2,0"
FontSize="13" />
</DockPanel>
</StackPanel>
<Grid>
<DataGrid ItemsSource="{Binding Path=TranslationsView}"
AutoGenerateColumns="False"
SelectionMode="Single"
CanUserAddRows="True">
<DataGrid.Columns>
<!-- your columns definition is here-->
</DataGrid.Columns>
</DataGrid>
<!-- your "busy indicator", that shows to user a message instead of stuck data grid-->
<Border Visibility="{Binding IsBusy,Converter={converters1:BooleanToSomethingConverter TrueValue='Visible', FalseValue='Collapsed'}}"
Background="#50000000">
<TextBlock Foreground="White"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="Loading. . ."
FontSize="16" />
</Border>
</Grid>
</DockPanel>
This pattern allows to work with data grid in a quite simple way and code is very simple either.
Do not forget to create default constructor for class that represents your data source.