I am entirely new to WPF and the implementation of MVVM Pattern, therefore excuse me if what I did so far can be seen close to blasphemy in certain people's eyes.
My (partial) goal as it follows:
Open main window which contains two Comboboxes labeled System and Documents
Upon loading this window I connect to an .accdb file that contains a table with System and Document columns.
It creates a list of all the distinct System names stored in the database file and stores them as an arrayList to which my "System" combobox is bound to. It successfully fills up the combobox and chooses the first member of the list as a selected item.
The selectedItem of the "System" combobow is OneWayToSource bound to a string called SystemFilter. Anytime I changed hte selection this string is succesfully updated.
PROBLEMATIC PART: I also want to filter out the documents belonging to the selected system and fill up the "Document" combobox. The helper method I wrote successfully does it using the SystemFilter string, and created an arrayList with the list of the Documents. However the "Document" Combobox is not updated even though it is bound to this arrayList. The interesting thing that sometimes if I run this helper method with a hardwired String argument it correctly updates the combobox.
My MainWindow.xaml (relevant part):
<!--System-->
<TextBlock Margin="5 0 0 0" Text="System" FontWeight="Bold" HorizontalAlignment="Left"/>
<ComboBox SelectionChanged="Combobox_Doc_Sys_SelectionChanged" x:Name="Combobox_Doc_Sys"
ItemsSource="{Binding mSystemList}" SelectedItem="{Binding mSystemFilter,Mode=OneWayToSource}"
SelectedIndex="0" Padding="2" Margin="5 0 5 0" >
</ComboBox>
<!--Document-->
<TextBlock Margin="5 10 0 0" Text="Document" FontWeight="Bold" HorizontalAlignment="Left"/>
<ComboBox x:Name="Combobox_Doc_Doc" ItemsSource="{Binding mTitleList}" SelectedIndex="0"
Padding="2" Margin="5 0 5 0">
</ComboBox>
The code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
private void Combobox_Doc_Sys_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MainWindowViewModel.GetListOfTitle();
}
}
MainWindowViewModel:
class MainWindowViewModel : BaseViewModel
{
static ArrayList SystemList = new ArrayList();
public static ArrayList mSystemList { get { return SystemList; } set { } }
//--------------------------------------------------------------------------------------------//
static ArrayList TitleList = new ArrayList();
public static ArrayList mTitleList { get { return TitleList; } set { } }
//--------------------------------------------------------------------------------------------//
static ArrayList RevisionList = new ArrayList();
public static ArrayList mRevisionList { get { return RevisionList; } set{ } }
//--------------------------------------------------------------------------------------------//
public static ObservableCollection<DocumentModel> DocumentList = new ObservableCollection<DocumentModel>();
//--------------------------------------------------------------------------------------------//
static string SystemFilter = String.Empty;
public static string mSystemFilter{get { return SystemFilter; } set { SystemFilter = value; } }
//--------------------------------------------------------------------------------------------//
static readonly string ConnectStringDocList = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\\Model\\Data\\DocumentList.mdb";
public MainWindowViewModel()
{
GetListofSystems();
GetListOfTitle();
}
public static void GetListofSystems()
{
//SQL for gettint all the distinctive values from the system column
string listSQL = "SELECT DISTINCT System FROM DocList";
SystemList.Clear();
//Creating new connection
using (OleDbConnection MyConnection = new OleDbConnection(ConnectStringDocList))
{
//Create command
OleDbCommand command = new OleDbCommand(listSQL, MyConnection);
command.Connection = MyConnection;
MyConnection.Open();
//Create reader
OleDbDataReader reader = command.ExecuteReader();
//With given parameter "listSQL" we iterate through the "system" column of DocumentList.mdb
// and wee fill up "SystemList" with all the distinctive values
while (reader.Read())
{
string item = reader.GetString(0);
SystemList.Add(item);
}
//Close reader
reader.Close();
//Close connection
MyConnection.Close();
}
}
public static void GetListOfTitle()
{
//List that will store the list of the title
//filtering SQL
string sysFilterSQL = $"SELECT * FROM DocList WHERE System='{SystemFilter}'";
//Clear List for safety sake
TitleList.Clear();
using (OleDbConnection MyConnection = new OleDbConnection(ConnectStringDocList))
{
OleDbCommand command = new OleDbCommand(sysFilterSQL, MyConnection);
command.Connection = MyConnection;
MyConnection.Open();
OleDbDataReader reader = command.ExecuteReader();
//We fill up TitleList with the list of the titles filtered by "system"
while (reader.Read())
{
var item = reader.GetString(2);
TitleList.Add(item);
}
}
}
And the BaseViewModel:
public class BaseViewModel : INotifyPropertyChanged
{
/// <summary>
/// The event that is fired when any child poperty changes its value
/// </summary>
public event PropertyChangedEventHandler PropertyChanged = ( sender, e) =>{ };
}
Thank you for the answers in advance. I was trying to find a clear straightforward tutorial on this but no success. How do I notify properly my UI that the source has changed...
You want to change something in the ViewModel and it should update the view. This only works by using NotifyPropertyChanged for properties and INotifyCollectionChanged for collections. In your case, just use ObservableCollection<string>, it has already both implemented.
public ObservableCollection<string> TitleList { get; set; } = new ObservableCollection<string>();
Use TitleList as your ItemSource in the view instead of mTitleList. And add stuff directly to it in the ViewModel like that (I am not 100% sure with the syntax, you have to try out or google it)
TitleList.Add("my stuff");
Some additonal notes
for me it is quite hard to read your code, because I am not used with the nameing convention you are using. If you are allowed at work, switch to microsoft c# naming convention: Property names in PascalCase (TitleList) and field names camelcase with _-prefix (_titleList).
First time I see ArrayList being used. If you have a certain reason for this, keep it. But usually we use List<string> for everything that would be a string array in other languages.
your BaseViewModel is insufficient. In the future you may want to be able to raise the PropertyChanged event in your ViewModel with a property name in the EventArgs. Important Note: you fire the PropertyChanged event in your ViewModel. Noone else does that. The view (or rather the binding class) subscribes to the PropertyChange Event of your ViewModel with an eventHandler, so that it is notified, when you raise the PropertyChanged event in your ViewModel Some more information about it
should those fields be backing fields for the properties? if yes, then you made them wrong. This is how it is done:
private string _myText;
public bool MyText
{
get => _myText;
set { _myText = value; // raise notify property changed here, if view should be updated }
}
Thanks for the quick answer Blechdose it helped a lot.
For the naming convention: It is not for my work, just a hobby project, so no issue changing in the way you proposed it.
I realized I did not applied INotifyPropertyChanged correctly. So, I rewrote it, than after a little bit of search I realized if I used FodyPropertyChanged NuGet package to weave my assembly my code could be a tad cleaner.
I also changed my arrayLists to ObservableCollections. ( I only used arrayList cause as being new to programming it seemed like a good fit)
class MainWindowViewModel : BaseViewModel
{
public static ObservableCollection<string> SystemList { get; set; } = new ObservableCollection<string>();
//--------------------------------------------------------------------------------------------//
public static ObservableCollection<string> TitleList { get; set; } = new ObservableCollection<string>();
//--------------------------------------------------------------------------------------------//
public static ObservableCollection<string> RevisionList { get; set; } = new ObservableCollection<string>();
//--------------------------------------------------------------------------------------------//
public static ObservableCollection<DocumentModel> DocumentList { get; set; } = new ObservableCollection<DocumentModel>();
//--------------------------------------------------------------------------------------------//
public static string SystemFilter { get; set; } = String.Empty;
//--------------------------------------------------------------------------------------------//
And as for BaseViewModel I kept:
public class BaseViewModel : INotifyPropertyChanged
{
/// <summary>
/// The event that is fired when any child poperty changes its value
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
}
And I installed Fody PropertyChanged so I do not have to set everytime something like this:
private string mTest;
public string Test
{
get
{
return mTest;
}
set
{
if (mTest == value)
return;
mTest = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Test)));
}
}
Thanks for the feedback. It works correctly now. (So far at least :))
Related
I am struggling with Text binding in my WPF app.
Lets imagine that I have another working app (ex. windows service) with some data in it.
In my WPF app I would like to have folder "DATA" with class where data are introduced and in same folder another class which would include a void which will query my windows service
I would like to show this data in my WPF window.
To make it simpler - one class with data, one class with data changing and WPF window with showing this data.
Unfortunately I can not achieve this... When I am executing below code, my window is showing 0 instead 123.
I would like to achive that my window will show value 123.
file "Database.cs" in folder "Data" in project "example"
namespace example.Data
{
public class Database
{
private int _testInt = 0;
public int testInt
{
get { return _testInt; }
set { _testInt = value; }
}
}
}
file "Query.cs" in folder "Data" in project "example"
namespace example.Data
{
public class Query
{
public Database _database;
public void execute()
{
_database = new Database();
_database.testInt = 123;
}
}
}
file "MainWindow.xaml.cs" in project "example"
namespace example
{
public partial class MainWindow : Window
{
public Data.Database _database;
public Data.Query _query;
public int testInt
{
get { return _database.testInt; }
set { _database.testInt = value; OnPropertyChanged(); }
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
_database = new Data.Database();
_query = new Data.Query();
_query.execute();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion
}
}
File MainWindow.xaml
<Window>
<TextBlock Text="{Binding testInt}"
Foreground="White"
FontSize="15"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="20,10,10,0" />
</Window>
P.S. If I will put
_database.testInt = 987;
to MainWindow.xaml.cs it is working properly - window is showing value 987 in textblock.
You have multiple instances of the Database object, a new one each time Query.execute is called and one in MainWindow constructor.
It's the data in the later that is displayed.
You should modify the content of this instance to see any change, for that, you must inject it in the Query object:
_query = new Data.Query(_database);
// ...
public class Query
{
private readonly Database _database;
public Query(Database database)
{
_database = database;
}
public void Execute()
{
_database.testInt = 123;
}
}
Finally you need a way to notify the view that the content as changed, that why Database should implement INotifyPropertyChanged.
But at this point it's badly named, because it's a model in the MVVM pattern.
you need to implement INotifyPropertyChanged
public partial class MainWindow : Window, INotifyPropertyChanged
from the MVVM view, I think these answers from Orace and Jason are on a good way, both do not solve the problem completely.
Let the Mainwindow implement INotifyPropertyChanged
Let the query accept the new value:
public void execute(int value)
{
//_database = new Database();
// inject _database like in the answer above
_database.testInt = value;
}
When your testInt changes, let the _query deliver the change down to the "database" (btw: you do it vice versa) See code below:
`public int testInt
{get { return _database.testInt; }
`set { _query.execute(value); OnPropertyChanged(); }`
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
_database = new Data.Database();
// the property change will change both the view and the model
testInt = 987;
}
Well, you have changed both model and view with one property change then, Good or not?!
Just for future users. There is small bug in Orace's answer: (It should be without "readonly" parameter, because below You are writing to it.
private Database _database;
public Query(Database database)
{
_database = database;
}
This is my first post in this forum, though I am a long-time lurker. I have started learning WPF for about a couple of months now, and I am trying to create an application just for training purposes.
I have a backend database which I have added to my application using EF6 ORM. In my application, I have a `ComboBox which needs to be populated by a column in a table of the database. That I can do using binding to a list.
The part I am having trouble with is the DataGrid. The columns of the DataGrid needs to be populated according to the Item chosen in the ComboBox.
My database:
As you can see, the school has several departments, and each of those department has a HOD and a student strength.
My application:
The ComboBox will be populated with school names. The DataGrid will be populated once the schoolname is selected. The DataGrid will have each row for each department available for the school. So I need to bind the corresponding columns with the departments of the corresponding schools. That much I get. However, then I want to save the user-entered comments in the Feedback TextBox.
I cannot understand how to create a class so that I can bind the DataGrid to the object of it. Is it possible to bind the DataGrid to an object and then bind the columns separately to another object?
EDIT
Apart from the entities created from the database, I have two classes:
class Feedback : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _school;
public string School
{
get
{
return _school;
}
set
{
_school = value;
OnPropertyChanged("School");
}
}
private ObservableCollection<FeedbackLine> _feedbackLines;
public ObservableCollection<FeedbackLine> FeedbackLines
{
get
{
return _feedbackLines;
}
set
{
_feedbackLines = value;
OnPropertyChanged("FeedbackLines");
}
}
public Feedback(string school)
{
//Insert some Linq Query to populate the FeedbackLines
//something like
//var FeedbackLines = Context.Schools.Where(c => c.SchoolName == school)
// .Select(c => new {Department = c.AvailableDepts.Dept, etc etc}.ToList();
//but then what?
}
private void OnPropertyChanged(string v)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
}
}
This is supposed to be bound to the datagrid. And the FeedbackLine is:
public class FeedbackLine: INotifyPropertyChanged
{
private string _dept;
public string Department
{
get { return _dept; }
set { _dept = value;
OnPropertyChanged("Department");
}
}
private string _HOD;
public string HOD
{
get { return _HOD; }
set { _HOD = value;
OnPropertyChanged("HOD");
}
}
private int _strength;
public int Strength
{
get { return _strength; }
set { _strength = value;
OnPropertyChanged("Strength");
}
}
private bool _isSelected;
public bool Selected
{
get { return _isSelected; }
set { _isSelected = value;
OnPropertyChanged("Selected");
}
}
private string _comment;
public string Comment
{
get { return _comment; }
set { _comment = value;
OnPropertyChanged("Comment");
}
}
private void OnPropertyChanged(string v)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
}
public event PropertyChangedEventHandler PropertyChanged;
}
I haven't had much headway with the ViewModel. Problem is, I am not very good with LINQ. And there are too many classes and objects and I have no idea which one to bind with which. The only vague idea that I can get is that I have to use LINQ to query the database using the selected School, and then populate the FeedbackLines using that.
Edit 2:
For anyone who's interested, here's my model diagram in WPF:
Model Diagram
Edit 3:
I think I am confused about ViewModel. The data that will be displayed on the screen is not necessarily the data to be saved. For example, I don't want to save the unselected rows. My Feedback class tries to display the data as well as save it. And therein lies the problem. Can't a DataGrid be bound to an object, while its columns be bound to other objects? For example, if I choose to use a Combobox for Department. Then I need to use ItemsSource for displaying items, but need to save the SelectedItem only. I can't find a way to separate these two concerns.
I would change your Feedback constructor
public Feedback(string school, List<FeedbackLine> feedbackLines)
{
School = school;
FeedbackLines = new ObservableColleytion<FeedbackLine>(feedbackLines);
}
It's a better architecture if your data viewmodel does not have a connection to the database. You can put your select in a seperate class.
If you need help with your LINQ statement I can help you.
In your Feedback constructor you wrote
//but then what?
When you got your data you can create instances of FeedbackLines to add them in the new constructor I showed above.
When you did this your viewmodel (which is DataContext of your view) needs an
public void ObservableCollection<Feedback> Feedbacks
with INotifyPropertyChanged like you did it in the other viewmodels.
In your xaml you have your ComboBox with the schools. Give that combobox a name, e.g. SchoolsComboBox.
In your DataGrid write this line
Source={Binding ElementName=SchoolsComboBox, Path=SelectedItem.FeedbackLines}
/edit for adding LINQ
You created an anonymous type. Just create a FeedbackLine instead and you're fine.
var feedbackLines = Context.Schools.Where(c => c.SchoolName == school)
.Select(c => new FeedbackLine
{
Department = c.AvailableDepts.Dept,
HOD = c.AvailableDepts.HeadOfDept,
Strength = c.AvailableDepts.StudentStrength}
.ToList()
U can make Something like this. Im sure it can be writen better but it works.
In your ViewModel make 3 Properties that implements INotifyPropertyChanged
One for your collection that will you bind to your ComboBox(make it ObservableCollection), One for SelectedItem from your ComboBox( you bind it to SelectedItem in comboBox) and another ObservableCollection that you will Bind to DataGrid)
For example you have in XAML:
<Grid>
<ComboBox ItemsSource="{Binding Products}"
SelectedItem="{Binding SelectedProduct}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="200"
Margin="20"
IsSynchronizedWithCurrentItem="True" />
<DataGrid ItemsSource="{Binding SelectedOne}"
HorizontalAlignment="Right "
VerticalAlignment="Center"
Width="300"
IsSynchronizedWithCurrentItem="True">
</DataGrid>
and in your ViewModel you can have something like this.
public ObservableCollection<Product> Products
{
get { return _products; }
set
{
if (value != _products)
{
_products = value;
OnPropertyChanged();
}
}
}
private ObservableCollection<Product> _selectedOne;
public ObservableCollection<Product> SelectedOne
{
get { return _selectedOne; }
set {
_selectedOne = value;
OnPropertyChanged();
}
}
public int SelectedProductId
{
get { return _selectedProductId; }
set
{
if (value != _selectedProductId)
{
_selectedProductId = value;
OnPropertyChanged();
}
}
}
public Product SelectedProduct
{
get { return _selectedProduct; }
set
{
if (value ! = _selectedProduct)
{
_selectedProduct = value;
// clear your list of selected objects and then add just selected one
// or you dont clear it, and items will be added in DataGrid when selected in ComboBox
SelectedOne.Clear();
SelectedOne.Add(_selectedProduct);
OnPropertyChanged();
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
Code to Populate Products, DataGrid will be populated by selecting Item from ComboBox.
u can go in ViewModel constructor and make something like this.
public MainWindowViewModel()
{
if (!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
using (YourDbContext context = new YourDbContext ())
{
var productList = new ObservableCollection<Product>(context.Products);
productList.ToList()
Products = productsList;
}
}
}
In my XAML code I've got a combo box that is bound to a static property as shown below.
<ComboBox x:Name="DifferentKinds"
ItemsSource="{x:Static local:MainWindow.DifferentKinds}"/>
And the code for the property and its source.
public static Kind[] DifferentKinds
=> (Kind[])Enum.GetValues(typeof(Kind));
public enum Kind { WeeHee, BuuHuu }
I just learned that there'll be more kinds in the future. They won't be created particularly often but it's uncertain how many they might become with time. So, instead of adding new elements to the enum, I'll read in these from the DB.
For the simplicity of the example, let's say we read in those values every time the property is accessed. The solution becomes a private fields that is read in from the DB before the execution of InitializeComponent() starts. Then, I serve those values as a static property still, like so.
public MainWindow()
{
PopulateDifferentKinds();
InitializeComponent();
}
private static IEnumerable<Kind> _allDifferentKinds;
public static IEnumerable<Kind> AllDifferentKinds
=> _allDifferentKinds.Where(element => element.Active);
public class Kind
{
public String Name { get; set; }
public bool Active { get; set; }
public override string ToString() { return Name; }
}
Is this approach creating a huge problem that I miss to see?
Is there a better way to bind the items in the bombo box to the values from DB?
The main problem I see here is that calling the PopulateDifferentKinds method in the view's constructor will create a performance problem. While this method is running and the database is being queried, your UI is being blocked.
This could be improved using a class that loads your data on a background thread and uses a PropertyChanged event to signal that the data has been loaded:
public class Kind
{
public string Name { get; set; }
public bool Active { get; set; }
public int Value { get; set; }
}
public class AppEnumValues : INotifyPropertyChanged
{
private static readonly Lazy<AppEnumValues> current
= new Lazy<AppEnumValues>(() => new AppEnumValues(), LazyThreadSafetyMode.ExecutionAndPublication);
public static AppEnumValues Current
{
get { return current.Value; }
}
public Kind[] AllDifferentKinds { get; private set; }
public bool IsLoaded { get; private set; }
private AppEnumValues()
{
Task.Run(() => this.LoadEnumValuesFromDb())
.ContinueWith(t => this.OnAllPropertiesChanged());
}
protected virtual void OnAllPropertiesChanged()
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(string.Empty));
}
}
private void LoadEnumValuesFromDb()
{
// This simulates some latency
Thread.Sleep(2000);
// Call your data service here and load the values
var kinds = new[]
{
new Kind {Active = true, Name = "WeeHee", Value = 1},
new Kind {Active = true, Name = "BuuHuu", Value = 2}
};
this.AllDifferentKinds = kinds;
this.IsLoaded = true;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
You could extend this with properties for each extensible "enum" you need in your application. Implementing the Singleton pattern, this would load its data in background the first time it is used. You could bind your ComboBoxes like this:
<ComboBox ItemsSource="{Binding Source={x:Static wpfApplication2:AppEnumValues.Current},Path=AllDifferentKinds}"
IsEnabled="{Binding Source={x:Static wpfApplication2:AppEnumValues.Current},Path=IsLoaded}"
DisplayMemberPath="Name" />
While the data is being loaded, the ComboBox would be disabled.
I would recommend looking into MVVM and Dependency Injection. This will enhance your WPF application architecture and make things like that easy: You wouldn't provide a static property or singleton, which has bad testability and extensibility, but you could use constructor injection to give the AppEnumValues provider into your View Model and then bind your view to it.
I am trying to set up a multi-language application, so when the user changes the display language all the texts in all the open windows change automatically.
I am having issues through with binding combo-box control. The binding needs to be done in code-behind as I have dynamic content coming from a database, and sometimes I even have to create additional combo-boxes at runtime.
Also I do not want to keep the translations in the database because I do not want to query the database every time a user is changing the display language.
What I did until now:
in xaml:
<ComboBox x:Name="cmb"/>
and in C#:
public class MyCmbItem
{
public int Index { get; set; }
public string Text { get; set; }
}
private ObservableCollection<MyCmbItem> LoadText()
{
ObservableCollection<MyCmbItem> _result = new ObservableCollection<MyCmbItem>();
foreach (var _item in _list)
{
//the list is coming from a database read
_result.Add(new MyCmbItem { Index = _item.Value, Text = _res_man_global.GetString(_item.KeyText, _culture) });
}
return _result;
}
public ObservableCollection<MyCmbItem> MyTexts
{
get { return LoadText(); }
set {} //I do not have to add/remove items at runtime so for now I leave this empty
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
...
LoadList(); //this adds values in _list
cmb.ItemsSource = MyTexts; //this populates the combo-box
Here I got stuck and I do not know how to determine the combo-box to refresh the displayed texts. The method must achieve that if I have several windows opened each containing a random number of combo-boxes, when I change the current language all the combo-boxes in all the windows will refresh the displayed list, without affecting other values inside (like the selected item). Does anybody know how this can be done?
Many thanks.
For your xaml UI, the INotifyPropertyChanged interface indicates updates of the viewmodel. You can extend your class like this:
public class MyCmbItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string APropertyName)
{
var property_changed = PropertyChanged;
if (property_changed != null)
{
property_changed(this, new PropertyChangedEventArgs(APropertyName));
}
}
private string _Text;
private string _KeyText;
public int Index { get; set; }
public string Text
{
get { return _Text;}
set {
if (_Text != value)
{
_Text = value;
NotifyPropertyChanged("Text");
}
}
}
public MyCmbItem(string key_text, int index)
{
Index = index;
_KeyText = key_text;
RefreshText();
_res_man_global.LanguageChanged += () => RefreshText();
}
public void RefreshText()
{
Text = _res_man_global.GetString(_KeyText, _culture);
}
}
Your view can simply bind to the Text-property as following:
<DataTemplate DataType="{x:Type local:MyCmbItem}">
<TextBlock Text="{Binding Path=Text}"/>
</DataTemplate>
Note: I assumed that your language class is global and has some kind of language-changed notification event.
I am facing a ListBox's ItemsSource related issue. I am implementing MVVM with WPF MVVM toolkit version 0.1.
I set one ListBox itemSource to update when a user double clicks on some other element (I handled the event in the code behind and executed the command there, since binding a command to specific events are not supported). At this point through the execution of the command a new ObservableCollection of items get generated and the ListBox's ItemsSource is intended to get updated accordingly. But it is not happening at the moment. ListBox does not update dynamically. What can be the problem? I am attaching relvent code for your reference.
XAML:
List of items which is doubled click to generate the next list:
<ListBox Height="162" HorizontalAlignment="Left" Margin="10,38,0,0" Name="tablesViewList" VerticalAlignment="Top" Width="144" Background="Transparent" BorderBrush="#20EEE2E2" BorderThickness="5" Foreground="White" ItemsSource="{Binding Path=Tables}" SelectedValue="{Binding TableNameSelected, Mode=OneWayToSource}" MouseDoubleClick="tablesViewList_MouseDoubleClick"/>
Second list of items which currently does not get updated:
<ListBox Height="153" HorizontalAlignment="Left" Margin="10,233,0,0" Name="columnList" VerticalAlignment="Top" Width="144" Background="Transparent" BorderBrush="#20EEE2E2" BorderThickness="5" Foreground="White" ItemsSource="{Binding Path=Columns, Mode=OneWay}" DisplayMemberPath="ColumnDiscriptor"></ListBox>
Code Behind:
private void tablesViewList_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
MainViewModel currentViewModel = (MainViewModel)DataContext;
MessageBox.Show("Before event command is executed");
ICommand command = currentViewModel.PopulateColumns;
command.Execute(null);
MessageBox.Show(currentViewModel.TableNameSelected);
//command.Execute();
}
View Model:
namespace QueryBuilderMVVM.ViewModels
{
//delegate void Del();
public class MainViewModel : ViewModelBase
{
private DelegateCommand exitCommand;
#region Constructor
private ColumnsModel _columns;
public TablesModel Tables { get; set; }
public ControllersModel Operators { get; set; }
public ColumnsModel Columns {
get { return _columns; }
set {
_columns = value;
OnPropertyChanged("Columns");
}
}
public string TableNameSelected{get; set;}
public MainViewModel()
{
Tables = TablesModel.Current;
Operators = ControllersModel.Current;
Columns = ColumnsModel.ListOfColumns;
}
#endregion
public ICommand ExitCommand
{
get
{
if (exitCommand == null)
{
exitCommand = new DelegateCommand(Exit);
}
return exitCommand;
}
}
private void Exit()
{
Application.Current.Shutdown();
}
//Del columnsPopulateDelegate = new MainViewModel().GetColumns;
//Method to be assigned to the delegate
//Creates an object of type ColumnsModel
private void GetColumns() {
ColumnsModel.TableNameParam = TableNameSelected;
Columns = ColumnsModel.ListOfColumns;
}
private ICommand _PopulateColumns;
public ICommand PopulateColumns
{
get {
if (_PopulateColumns == null) {
_PopulateColumns = new DelegateCommand(GetColumns); // an action of type method is passed
}
return _PopulateColumns;
}
}
}
}
Model:
public class ColumnsModel : ObservableCollection<VisualQueryObject>
{
private DataSourceMetaDataRetriever dataSourceTableMetadataObject;// base object to retrieve sql data
private static ColumnsModel listOfColumns = null;
private static object _threadLock = new Object();
private static string tableNameParam = null;
public static string TableNameParam
{
get { return ColumnsModel.tableNameParam; }
set { ColumnsModel.tableNameParam = value; }
}
public static ColumnsModel ListOfColumns
{
get
{
lock (_threadLock)
if (tableNameParam != null)
listOfColumns = new ColumnsModel(tableNameParam);
return listOfColumns;
}
}
public ColumnsModel(string tableName)
{
ColumnsModel.tableNameParam = tableName;
Clear();
try
{
dataSourceTableMetadataObject = new DataSourceMetaDataRetriever();
List<ColumnDescriptionObject> columnsInTable = new List<ColumnDescriptionObject>();
columnsInTable = dataSourceTableMetadataObject.getDataTableSchema("Provider=SQLOLEDB;Data Source=.;Integrated Security=SSPI;Initial Catalog=LogiwizUser", ColumnsModel.tableNameParam);
//List<String> listOfTables = dataSourceTableMetadataObject.getDataBaseSchema("Provider=SQLOLEDB;Data Source=.;Integrated Security=SSPI;Initial Catalog=LogiwizUser");
//List<String> listOfTables = dsm.getDataBaseSchema("G:/mytestexcel.xlsx", true);
//ObservableCollection<VisualQueryObject> columnVisualQueryObjects = new ObservableCollection<VisualQueryObject>();
foreach (ColumnDescriptionObject columnDescription in columnsInTable)
{
VisualQueryObject columnVisual = new VisualQueryObject();
columnVisual.ColumnDiscriptor = columnDescription;
columnVisual.LabelType = "column";
Add(columnVisual);
}
}
catch (QueryBuilderException ex)
{
/* Label exceptionLabel = new Label();
exceptionLabel.Foreground = Brushes.White;
exceptionLabel.Content = ex.ExceptionMessage;
grid1.Children.Add(exceptionLabel);*/
}
}
}
Any help is greatly appreciated. Thanks in advance.
The setter of property Columns should raise a PropertyChanged event.
Implement INotifyPropertyChanged to do so : MSDN INotifyPropertyChanged
I guess MVVM Toolkit provides a way of doing so easily (perhaps ViewModelBase already implement the interface ...).
EDIT : Implementing INotifyPropertyChanged is not enough, you have to raise the event created by INotifyPropertyChanged. You property should look something like this :
private ColumnsModel _columns;
public ColumnsModel Columns
{
get { return _columns; }
set
{
_columns = value;
PropertyChanged("Columns");
}
}
use an observableCollection<T> instead of a List<T>
MSDN DOC:
WPF provides the ObservableCollection class, which is a built-in implementation of a data collection that exposes the INotifyCollectionChanged interface. Note that to fully support transferring data values from source objects to targets, each object in your collection that supports bindable properties must also implement the INotifyPropertyChanged interface. For more information, see Binding Sources Overview.