I have a List Box in WPF and I want the content of the list to change everytime the values are changed in code.
At the start of my program I insert the default values in the ListBox and this works well.
using System; using System.Collections.Generic; using System.Linq;
using System.Text; using System.Threading.Tasks; using System.ComponentModel;
using System.Collections.ObjectModel;
public partial class MainWindow : Window
{
//here is my data which goes into the list
private DataTable _dataTable1 = null;
//list which goes into ListBox
private List<CompareListItem> _compareListItems1 = null;
public MainWindow()
{
InitializeComponent();
// ..Code missing which writes data in _dataTable
//ReloadCompareList fills the _compareListItems1 with data from _dataTable1
_compareListItems1 = ReloadCompareList(_dataTable1);
//here I do the binding to the ListBox
compareSelectionList1.ItemsSource = _compareListItems1;
}
But when I change the values here it does not affect the ListBox
//this method is called when i want to replace the entire _compareListItems1 list
private void tableList1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//this method write my data into _dataTable
_dataTable1 = ReloadTableList(tableList1, _dataTable1, tableGrid1);
if (_dataTable1 != null)
{
//the values of my compare list are replaced, but nothing happens with the ListBox
_compareListItems1 = ReloadCompareList(_dataTable1); // ESSENTIAL LINE
}
}
}
Each item in my ListBox will be a CompareListItem. I found here on stackoverflow the following topic about INotifyPropertyChanged and I implemented this here. It works when i update a single object in my list.
// Class for the items displayed in the Listbox of the compare list
public class CompareListItem : INotifyPropertyChanged
{
private string itemTitle;
public string ItemTitle
{
get{return itemTitle;}
set{
//this works when a single value in the list is changed, but not if i add or delete someting
SetField(ref itemTitle, value, "ItemTitle");
}
}
public CompareListItem(string title) {
//does not affect the data bindings, could be "itemTitle = title;" to
SetField(ref itemTitle, title, "ItemTitle");
}
//this is from https://stackoverflow.com/questions/1315621/implementing-inotifypropertychanged-does-a-better-way-exist
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;
}
}
edit: here the XAML of my ListBox:
<ListBox x:Name="compareSelectionList1" Margin="10,0,10,10" IsSynchronizedWithCurrentItem="False" Grid.Row="1" Height="100" VerticalAlignment="Bottom" SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,2">
<TextBlock Text="{Binding ItemTitle, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}"></TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When I add the following line it will work. But I think that is not the meaning of the approach with the DataBindings. I understood the approach like "you mention the view-element once in the code and then you never use the name "compareSelectionList1" again."
_compareListItems1 = ReloadCompareList(_dataTable1); // ESSENTIAL LINE
compareSelectionList1.ItemsSource = _compareListItems1;
How can I replace my list so that the ListBox will be updated to via the data-binding?
You're setting _compareListItems1 to a new instance of List<CompareListItem>, but compareSelectionList1.ItemsSource still refers to the previous instance. That's why you need to reassign ItemsSource for it to work.
But I think that is not the meaning of the approach with the DataBindings
Currently, you're not using a binding to set the ItemsSource, so it can't be refreshed automatically. To do that, you need to expose the list as a property, and implement INotifyPropertyChanged in your Window (another option is to expose it as a dependency property). In XAML, bind ItemsSource to the list property, and it will work as expected.
Related
I am very new to the concept of data binding and I don't think I understood it completely. I have a class named Project with a LinkedList of type ToDo as one of its properties. When I navigate to one instance of Project, I will display the LinkedList of type ToDo in a ListView. I have created functions that allow me to change the sequences of the nodes in the LinkedList (move up, move down) and to remove the selected node (delete). I want the ListView to refresh whenever there is a change in the LinkedList, (move up, move down or delete). However, I cannot achieve that. Here is my code: (not all parts are included)
XAML of the page:
<ListView x:Name="myListView" ItemsSource="{Binding Source={StaticResource ToDos}, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox x:Name="myCheckBox"
Content="{Binding ToDoTitle, Mode=TwoWay}"
IsChecked="{Binding IsCompleted, Mode=TwoWay}">
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
C# for DataModel:
public class ToDo : INotifyPropertyChanged
{
private string toDoTitle;
private bool isCompleted;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public string ToDoTitle { get { return this.toDoTitle; } set { this.toDoTitle = value; this.OnPropertyChanged(); } }
public bool IsCompleted { get { return this.isCompleted; } set { this.isCompleted = value; this.OnPropertyChanged(); } }
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Projects : INotifyPropertyChanged
{
private LinkedList<ToDo> toDos;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public LinkedList<ToDo> ToDos { get { return this.toDos; } set { this.toDos = value; this.OnCollectionChanged(); } }
public Projects()
{
ToDos = new LinkedList<ToDo>();
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Thank you.
First I would advise you to read about MVVM, and try to follow some basic tutorials like this one.
You can use MVVM Light to avoid managing the INotifyPropertyChanged by yourself at first (but it's really good to know how MVVM light work under the hood).
To come back to your problem, your current code notifies only if you set the full ToDos list. If you want to be aware of any change in a list (seing when an item is add/remove/update), you are probably looking for an ObservableCollection, not a LinkedList.
Hope it helps.
I'm very new to programming in Visual Studio and for Windows Phone 8. I do have knowledge in PHP, so I understand the basics of syntax, albeit a slight differentiation from C#, however.
Anyways, I am simply trying to make a list I've declared in a variable in \MainPage.xaml.cs viewable in \MainPage.xaml, via binding, I guess that's how it's done.
I'm trying to make it as basic as possible for now; here is what I have in the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using lbpme_viewer.Resources;
namespace lbpme_viewer
{
public partial class MainPage : PhoneApplicationPage
{
List<String> MenuItems = new List<String> { "portal", "news", "myprofile", "settings" };
// Constructor
public MainPage()
{
InitializeComponent();
}
}
}
And I would just make 4 plain textblocks, but I want to remove "myprofile" based on the user's settings, but before I get to that, right now I just declared them all in one list of strings.
And a portion of my XAML file:
<!--Panorama item one-->
<phone:PanoramaItem Header="first item">
<!--Single line list with text wrapping-->
<phone:LongListSelector x:Name="menuList" Margin="0,0,-22,0" ItemsSource="{Binding Path=MenuItems}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,-6,0,12">
<TextBlock Text="{Binding}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}" FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</phone:PanoramaItem>
And obviously the items won't show in the LongListSelector. I don't expect them to because I know I'm missing one or many pieces of code required to do so, and my question is, how?
Again, I just want the list of phrases in the MenuList variable to appear in my LongListSelector, like how "design one" "design two" does in the default panorama/pivot app upon creation.
And if it wouldn't be much different, how would I get any variable from the C# code to appear in the XAML file?
Thank you!
Try binding to an ObservableCollection<string> that implements INotifyPropertyChanged like this
List<String> MenuItemsList = new List<String> { "portal", "news", "myprofile", "settings" };
private ObservableCollection<string> _menuItems;
public ObservableCollection<string> MenuItems
{
get { return _menuItems; }
set
{
if (_menuItems == value) return;
_menuItems = value;
NotifyPropertyChanged(); // or NotifyPropertyChanged("MenuItems");
}
}
then in your constructor new up the ObservableCollection by passing in the List
MenuItems = new ObservableCollection<string>(MenuItemsList);
You also need this in your class (from http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx)
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Take a look at MVVM Light, it makes a lot of WPF things easier. With MVVM Light, your property could look like this (and you wouldn't need all the NotifyPropertyChanged stuff)
private ObservableCollection<string> _menuItems;
public ObservableCollection<string> MenuItems
{
get { return _menuItems; }
set
{
if (_menuItems == value) return;
_menuItems = value;
RaisePropertyChanged(() => MenuItems);
}
}
Hy,
I have a menu with a few menu items. I have various other elements like a treeview and some controls. When I open the program all elements in the menu are available. But the first step I have to do is to connect to the server. So all the the other elements shouldn't available till there is made a connection via the connection menu item.
Then I want to show only menu items if a special tree view (for instance the whole item structure) item is choosen for instance all topics. For instance there should be special menu items available if I click a treeview entry in the menu.
Is it possible to accomplish this in xaml?
Update1:
MainWindow.xaml
Title="Service Bus Visualizer" Height="680" Width="1200" Name="Root"
<MenuItem Header="_Read File" Name="readFile" Click="MenuItemReadFile" HorizontalAlignment="Right" Width="187" IsEnabled="{Binding Path=DataContext.IsMonitoring, ElementName=Root}">
<MenuItem.Icon>
<Image Source="Icons/Open.ico" Width="16" Height="16" />
</MenuItem.Icon>
</MenuItem>
MainWindow.xaml.cs
public bool IsMonitoring
{
get
{
return isMonitoring;
}
set
{
isMonitoring = value;
RaisePropertyChanged("IsMonitoring");
}
}
private bool isMonitoring;
public MainWindow()
{
InitializeComponent();
this.IsMonitoring = false;
this.DataContext = this;
Application.Current.MainWindow = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
ConnectionWindow.xaml.cs
MainWindow mainWindow = Application.Current.MainWindow as MainWindow;
mainWindow.IsMonitoring = true;
I get no error on the output window but it doesn't work?
Update2:
I have a second parameter which is a ObservableCollection.
MainWindow.xaml
<ListBox Grid.Row="3" Name="Logger" ItemsSource="{Binding Path=DataContext.LoggingList, ElementName=Root}" DisplayMemberPath="Message" IsSynchronizedWithCurrentItem="True" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Visible" SelectionChanged="BringSelectionIntoView">
</ListBox>
MainWindow.xaml.cs
public static ObservableCollection<Log> LoggingList { get; set; }
public MainWindow()
{
LoggingList = new ThreadSafeObservableCollection<Log>();
this.IsMonitoring = false;
this.DataContext = this;
Application.Current.MainWindow = this;
InitializeComponent();
}
Log.cs
public class Log : INotifyPropertyChanged
{
public string Message {
get
{
return message;
}
set
{
message = value;
NotifyPropertyChanged("Message");
}
}
public string message;
public Log()
{
}
public Log(string message)
{
this.Message = message;
NotifyPropertyChanged("Message");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
Best regards
First, you have two options as far as "availability" is concerned. The "IsEnabled" property, and the "Visible" property. "IsEnabled" is a bool, and determines if the user can click/select/interact with a given element. Generally speaking, if this property is set to false the element will appear "greyed out".
Changing Visibility will make the element appear/disappear entirely. When set to "Visible" (this is actually an enum), it appears normally. When set to "Hidden", the space for it is reserved on the UI, but you can't actually see it. When set to "Collapsed" you cannot see it and no space is reserved for it in the layout.
For your first requirement (waiting to connect to the server), I would use IsEnabled bound to a "IsConnected" property like so:
IsEnabled="{Binding IsConnected}"
That would go on each item that needs to have this behavior.
The "context-specific" menu items are a bit more complicated, but the basic idea would be a binding on Visible for each of the context sensitive items like:
Visible="{Binding Path=SelectedItem, ElementName=MyTreeView, Converter={StaticResource SelectedItemToVisibilityConverter}, ConverterParameter={x:Type ChildItem}"
I am assuming that each items visibility depends on what type of item is selected (child or parent), you should be able to extend the example if I was wrong. The converter would then look like:
public class SelectedItemToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((parameter as Type).IsInstanceOfType(value))
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(...)
{
return Binding.DoNothing;
}
}
Please let me know if I can clarify anything. Hopefully that gives you a good starting point for what you are trying to do.
Update:
Looking at your code I see a couple potential problems:
IsMonitoring is declared as a public field. Binding only works with public properties. This property needs to raise the PropertyChanged event for it to work.
In "MainWindow.xaml.cs" you are setting the DataContext multiple times. This isn't how DataContext works in WPF. You need to set it to one object (your ViewModel) that contains all the properties you are interested in binding to. While it is considered bad practice, you could write this.DataContext = this to get it working before you build a ViewModel class.
The IsMonitoring field is declared in your "MainWindow.xaml.cs" file. First, this should be in a view model. Second, the binding is looking for that property on the MenuItem class (likely because it is in some sort of ItemsControl). If you want it on the root data context, give your window some name (like "Root") and use the following binding:
"{Binding Path=DataContext.IsMonitoring, ElementName=Root}"
Hopefully that makes sense. Let me know if I can help further!
I have a WPF ViewModel
class MainWindowViewModel : INotifyPropertyChanged
{
private string _sql;
public string Sql
{
get { return _sql; }
set
{
if (value == _sql) return;
OnPropertyChanged("Sql");
_sql = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I also have a XAML view with a TextBox
<Window.Resources>
<HbmSchemaExporter:MainWindowViewModel x:Key="viewModel"/>
</Window.Resources>
....
<TextBox Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding Source={StaticResource ResourceKey=viewModel}, Path=Sql,Mode=OneWay}"/>
Code behind
private MainWindowViewModel ViewModel
{
get { return Resources["viewModel"] as MainWindowViewModel; }
}
The problem is that when in the code I do viewModel.Sql = SOMETHING the text box doesn't get updated. Debugger displays the correct value in the property but the textbox remains blank.
I also tried to change the binding to TwoWay but that only allows me to overwrite the property with a value I type in the textbox, which is something I don't really want (actually I still need to make it readonly, but it's currently out of scope).
How can I update the textbox after programmatically updating the property?
The application is basically a NHibernate DDL generator I'm writing after reading this. I need to press a "Generate SQL" button and it displays the code to run onto DB.
public string Sql
{
get { return _sql; }
set
{
if (value == _sql) return;
OnPropertyChanged("Sql");
_sql = value;
}
}
That does not make sense. At the point that any PropertyChanged event handler is called, reading Sql will still give the old value, because you haven't updated _sql yet. You need to first update the value, and only then raise the PropertyChanged event.
I am trying to get the get the sum to update in the textblock, however I'm only able to get it updated through restarting the windows phone emulator. Why is it so?
Code in DisplayBill.xaml
<TextBlock x:Name="lbTotalAmt" Text="{Binding Path=Sum, Mode=TwoWay, UpdateSourceTrigger=Explicit}" Margin="0,364,0,10" Grid.Row="1" />
Code in ViewModel.cs
private string _Sum;
public string Sum
{
get {return _Sum;}
set
{
_Sum = value;
NotifyPropertyChanged("Sum");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify Silverlight that a property has changed.
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
if (propertyName == "ToDoBills")
UpdateSumValue();
}
private void UpdateSumValue()
{
Sum = ToDoBills.Sum(i => i.Amount).ToString();
}
#endregion
Update
What I'm trying to do is to update the textblock everytime the listbox adds an item. so everytime a new item is added into the listbox, the textblock which display the total amount will update. So my question is how do I go about updating my textblock everytime a new item is added into the listbox? Can anyone help me please? I tried using the binding expression below but to no avail
public DetailPageBill()
{
InitializeComponent();
// Set the page DataContext property to the ViewModel.
this.DataContext = App.todoViewModel;
BindingExpression be = lbTotalAmt.GetBindingExpression(TextBlock.TextProperty);
be.UpdateSource();
}
Try setting UpdateSourceTrigger to PropertyChanged for your TextBlock's binding:
<TextBlock x:Name="lbTotalAmt" Text="{Binding Path=Sum, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0,364,0,10" Grid.Row="1" />
With Explicit no automatic update is performed. MSDN says:
Updates the binding source only when you call the
UpdateSource method.
See MSDN on UpdateSourceTrigger for more information.