Dynamically adding items to WPF listbox - c#

I'm trying to build an application that the user points to a folder of PDF files.
of Invoices, the program then parses the PDF files to find out wich ones contains an email address and wich ones don't. and this Is Where I'm stuck:
I then want to add the file names to either the Listbox for print or the Listbox for email.
I got all the other bits working, choosing the folder and parsing the PDF and adding the folder path to a textbox object.
I then run a function:
private void listFiles(string selectedPath)
{
string[] fileEntries = Directory.GetFiles(selectedPath);
foreach (string files in fileEntries)
{
try
{
ITextExtractionStrategy its = new iTextSharp.text.pdf.parser.LocationTextExtractionStrategy();
using (PdfReader reader = new PdfReader(files))
{
string thePage = PdfTextExtractor.GetTextFromPage(reader, 1, its);
string[] theLines = thePage.Split('\n');
if (theLines[1].Contains("#"))
{
// System.Windows.MessageBox.Show("denne fil kan sendes som email til " + theLines[1], "Email!");
}
else
{
System.Windows.MessageBox.Show("denne fil skal Printes da " + theLines[1] + " ikke er en email", "PRINT!");
}
}
}
catch (Exception exc)
{
System.Windows.MessageBox.Show("FEJL!", exc.Message);
}
}
}
And it is in this function I want to be able to add the files to either Listbox.
My XAML looks like this:
<Grid.Resources>
<local:ListofPrint x:Key="listofprint"/>
</Grid.Resources>
<ListBox x:Name="lbxPrint" ItemsSource="{StaticResource listofprint}" HorizontalAlignment="Left" Height="140" Margin="24.231,111.757,0,0" VerticalAlignment="Top" Width="230"/>
But I get the error: The name "ListofPrint" does not exist in the namespace "clr-namespace:test_app".
the ListofPrint is here:
public class ListofPrint : ObservableCollection<PDFtoPrint>
{
public ListofPrint(string xfile)
{
Add(new PDFtoPrint(xfile));
}
}
I've been trying to get the hang of the documentation on MSDN and have read 10 different similar Questions on this site, but I guess my problem is that I don't know exactly what my problem is. first of it's a data binding problem but I basically copied the sample from the documentation to play with but that is what is giving me the trouble.
Hopefully, someone here can explain to me the basics of data binding and how it corresponds to my ObservableCollection.

You need to create an instance of your collection class and bind the ListBox to it.
The most simple thing is setting its DataContext to this. I wrote an example:
Window:
public class MyWindow : Window
{
// must be a property! This is your instance...
public YourCollection MyObjects {get; } = new YourCollection();
public MyWindow()
{
// set datacontext to the window's instance.
this.DataContext = this;
InitializeComponent();
}
public void Button_Click(object sender, EventArgs e)
{
// add an object to your collection (instead of directly to the listbox)
MyObjects.AddTitle("Hi There");
}
}
Your notifyObject collection:
public class YourCollection : ObservableCollection<MyObject>
{
// some wrapper functions for example:
public void Add(string title)
{
this.Add(new MyObject { Title = title });
}
}
Item class:
// by implementing the INotifyPropertyChanged, changes to properties
// will update the listbox on-the-fly
public class MyObject : INotifyPropertyChanged
{
private string _title;
// a property.
public string Title
{
get { return _title;}
set
{
if(_title!= value)
{
_title = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs( nameof(Title)));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Xaml:
<ListBox ItemsSource="{Binding MyObjects}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Related

WPF - MVVM - How to update notify combobox of propertychange

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 :))

Binding content from other files in XAML

So I planning to bind label from two files or more, because I place the label and the cs file in separate way. For example:
SettingServicesPhone.xaml
<Label x:Name="sipLoginStatus"
Width="106"
Height="27"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="{Binding SipLoginStatusContent}"
FontSize="13" />
For the SettingServicePhone.xaml.cs I declared public String sipLoginStatusContent;
And I use Settings.xaml and Setting.xaml.cs as a container of all functions.
I've declared public static SettingsServicesPhone setCall = new SettingsServicesPhone(); on Setting.xaml.cs. And also write get set.
public String SipLoginStatusContent
{
get { return setCall.sipLoginStatusContent; }
set
{
if (setCall.sipLoginStatusContent != value)
{
setCall.sipLoginStatusContent = value;
OnPropertyChanged("SipLoginStatusContent"); // To notify when the property is changed
}
}
}
And here the example of onclick button that I stated on Settings.xaml.cs
public void applyBtn_Click(object sender, RoutedEventArgs e)
{
SipLoginStatusContent = "Logging In";
}
It's work fine if I included them in one file. But seems like it doesn't running if I make it separate. Am I doing it wrong way? Thank you.
Set the DataContext of the window where the Label is defined to an instance of the class where the SipLoginStatusContent property is defined:
public partial class Settings : Window
{
public static SettingsServicesPhone setCall = new SettingsServicesPhone();
public Settings()
{
InitializeComponent();
DataContext = this; //<--
}
public String SipLoginStatusContent
{
get { return setCall.sipLoginStatusContent; }
set
{
if (setCall.sipLoginStatusContent != value)
{
setCall.sipLoginStatusContent = value;
OnPropertyChanged("SipLoginStatusContent"); // To notify when the property is changed
}
}
}
public void applyBtn_Click(object sender, RoutedEventArgs e)
{
SipLoginStatusContent = "Logging In";
}
}

Bind elements of List<string> to text blocks

I'm trying to make some sort of wheel spinning. I have 5 customized text blocks, text file with the list of values (it may consist of 1-1000 items). After reading the file I have a 'List fileValues' with its values. I decided to create another 'List wheel' which will contain up to 5 elements at the time and is expected to be bind to text blocks.
When one presses a spin button, last element of 'wheel' is removed and new element from 'values' is added to the beginning of the 'wheel' list.
In order UI will be responsive to changes in the list, it is good to bind each element in the 'wheel' to corresponding text block on UI. But what I tried to do up to this moment didn't work.
Here is what I tried to do (the code is a little bit dirty, but I try to make it work firstly).
5 customized text blocks
<TextBlock Name="Value1" TextWrapping="WrapWithOverflow"/>
<TextBlock Name="Value2" TextWrapping="WrapWithOverflow"/>
<TextBlock Name="Value3" TextWrapping="WrapWithOverflow"/>
<TextBlock Name="Value4" TextWrapping="WrapWithOverflow"/>
<TextBlock Name="Value5" TextWrapping="WrapWithOverflow"/>
ObservableList which implements INotifyCollectionChanged interface
class ObservableList : INotifyCollectionChanged, IEnumerable
{
private readonly List<string> _valuesList;
public string First
{
get { return _valuesList.First(); }
}
public string Last
{
get { return _valuesList.Last(); }
}
public ObservableList()
{
this._valuesList = new List<string>();
}
public string this[Int32 index]
{
get
{
if (_valuesList.Count == 0 || index + 1 > _valuesList.Count)
{
return "------";
}
return _valuesList[index];
}
}
public void AddLast(string value)
{
_valuesList.Add(value);
OnNotifyCollectionChanged();
}
public void AddFirst(string value)
{
_valuesList.Insert(0, value);
OnNotifyCollectionChanged();
}
public void RemoveFirst()
{
_valuesList.RemoveAt(0);
OnNotifyCollectionChanged();
}
public void RemoveLast()
{
_valuesList.Remove(_valuesList.Last());
OnNotifyCollectionChanged();
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
public void OnNotifyCollectionChanged()
{
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
public IEnumerator GetEnumerator()
{
return (_valuesList as IEnumerable).GetEnumerator();
}
}
XAML Code-behind
public partial class MainWindow : Window
{
private List<string> _values = new List<string>();
private ObservableList _uiValues = new ObservableList();
public MainWindow()
{
InitializeComponent();
Value1.DataContext = _uiValues[0];
Value2.DataContext = _uiValues[1];
Value3.DataContext = _uiValues[2];
Value4.DataContext = _uiValues[3];
Value5.DataContext = _uiValues[4];
}
private void LoadFileBtn_Click(object sender, RoutedEventArgs e)
{
//Loads text file and fills _values
}
private void SpinBtn_Click(object sender, RoutedEventArgs e)
{
InitUiTextBlocks();
//Spin simulation
}
private void InitUiTextBlocks()
{
_uiValues.Clear();
for (int i = 0; i < 5; ++i)
{
//Nothing appears on UI and CollectionChanged event is null
_uiValues.AddLast(_values.First());
_values.RemoveAt(0);
}
}
}
I tried to use 'ObservableCollection', but the effect is the same. Nothing appears on UI. In fact I can't imagine how to bind each of List element to specific Label. Is it even possible to do such binding?
In the XAML do something like:
<Label Name="some_name" Content="{Binding SomeStingProperty}"/>
and in the code behind, have a
public string SomeStringProperty {get; set;}
you can bind to a collection as well, and if it's an ObservableCollection it will update on change.
search for basic XAML binding otherwise :)
(on a side note, it's cleaner i think it the XAML, i personally don't like to do it in the code behind ...)
As a side note, and totally self promoting, here are 2 articles that will probably help:
Understanding selected value
The big mvvm template.
The second might be a bit over your head if you're a beginner, but should be worth reading nevertheless.

How to bind a combo-box to a collection of multi-language values in WPF?

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.

Trouble copying a Grid object from one TabItem to another

In my program I have tabItems that have their commands bound to a View Model. I am in the process of implementing a function that will copy the design structure of a "master" tabItem, along with it's command functionality in order to create a new tabItem. I need to do this because the user of this program will be allowed to add new tabItems.
Currently I am using the question Copying a TabItem with an MVVM structure, but I seem to be having trouble when the function tries to copy the Grid object using dependencyValue.
The class I am using:
public static class copyTabItems
{
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
}
When dependencyValue gets to {[Content, System.Windows.Controls.Grid]} the program throws an InvalidOperationException was Unhandled stating that, "Specified element is already the logical child of another element. Disconnect it first".
What does this mean? Is this a common problem with the Grid in WPF (am I breaking some rule by trying to do this?)? Is there something in my program that I am not aware of that is causing this?
Ok. This is how you're supposed to deal with a TabControl in WPF:
<Window x:Class="MiscSamples.MVVMTabControlSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MiscSamples"
Title="MVVMTabControlSample" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Tab1ViewModel}">
<!-- Here I just put UI elements and DataBinding -->
<!-- You may want to encapsulate these into separate UserControls or something -->
<StackPanel>
<TextBlock Text="This is Tab1ViewModel!!"/>
<TextBlock Text="Text1:"/>
<TextBox Text="{Binding Text1}"/>
<TextBlock Text="Text2:"/>
<TextBox Text="{Binding Text2}"/>
<CheckBox IsChecked="{Binding MyBoolean}"/>
<Button Command="{Binding MyCommand}" Content="My Command!"/>
</StackPanel>
</DataTemplate>
<!-- Here you would add additional DataTemplates for each different Tab type (where UI and logic is different from Tab 1) -->
</Window.Resources>
<DockPanel>
<Button Command="{Binding AddNewTabCommand}" Content="AddNewTab"
DockPanel.Dock="Bottom"/>
<TabControl ItemsSource="{Binding Tabs}"
SelectedItem="{Binding SelectedTab}"
DisplayMemberPath="Title">
</TabControl>
</DockPanel>
</Window>
Code Behind:
public partial class MVVMTabControlSample : Window
{
public MVVMTabControlSample()
{
InitializeComponent();
DataContext = new MVVMTabControlViewModel();
}
}
Main ViewModel:
public class MVVMTabControlViewModel: PropertyChangedBase
{
public ObservableCollection<MVVMTabItemViewModel> Tabs { get; set; }
private MVVMTabItemViewModel _selectedTab;
public MVVMTabItemViewModel SelectedTab
{
get { return _selectedTab; }
set
{
_selectedTab = value;
OnPropertyChanged("SelectedTab");
}
}
public Command AddNewTabCommand { get; set; }
public MVVMTabControlViewModel()
{
Tabs = new ObservableCollection<MVVMTabItemViewModel>();
AddNewTabCommand = new Command(AddNewTab);
}
private void AddNewTab()
{
//Here I just create a new instance of TabViewModel
//If you want to copy the **Data** from a previous tab or something you need to
//copy the property values from the previously selected ViewModel or whatever.
var newtab = new Tab1ViewModel {Title = "Tab #" + (Tabs.Count + 1)};
Tabs.Add(newtab);
SelectedTab = newtab;
}
}
Abstract TabItem ViewModel (you to derive from this to create each different Tab "Widget")
public abstract class MVVMTabItemViewModel: PropertyChangedBase
{
public string Title { get; set; }
//Here you may want to add additional properties and logic common to ALL tab types.
}
TabItem 1 ViewModel:
public class Tab1ViewModel: MVVMTabItemViewModel
{
private string _text1;
private string _text2;
private bool _myBoolean;
public Tab1ViewModel()
{
MyCommand = new Command(MyMethod);
}
public string Text1
{
get { return _text1; }
set
{
_text1 = value;
OnPropertyChanged("Text1");
}
}
public bool MyBoolean
{
get { return _myBoolean; }
set
{
_myBoolean = value;
MyCommand.IsEnabled = !value;
}
}
public string Text2
{
get { return _text2; }
set
{
_text2 = value;
OnPropertyChanged("Text2");
}
}
public Command MyCommand { get; set; }
private void MyMethod()
{
Text1 = Text2;
}
}
Edit: I forgot to post the Command class (though you surely have your own)
public class Command : ICommand
{
public Action Action { get; set; }
public void Execute(object parameter)
{
if (Action != null)
Action();
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public Command(Action action)
{
Action = action;
}
}
And finally PropertyChangedBase (just a helper class)
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Result:
Basically, each Tab Item type is a Widget, which contains its own logic and Data.
You define all logic and data at the ViewModel or Model level, and never at the UI level.
You manipulate the data defined in either the ViewModel or the Model level, and have the UI updated via DataBinding, never touching the UI directly.
Notice How I'm leveraging DataTemplates in order to provide a specific UI for each Tab Item ViewModel class.
When copying a new Tab, you just create a new instance of the desired ViewModel, and add it to the ObservableCollection. WPF's DataBinding automatically updates the UI based on the Collection's change notification.
If you want to create additional tab types, just derive from MVVMTabItemViewModel and add your logic and data there. Then, you create a DataTemplate for that new ViewModel and WPF takes care of the rest.
You never, ever, ever manipulate UI elements in procedural code in WPF, unless there's a REAL reason to do so. You don't "uncheck" or "disable" UI Elements because UI elements MUST reflect the STATE of the data which is provided by the ViewModel. So a "Check/Uncheck" state or an "Enabled/Disabled" state is just a bool property in the ViewModel to which the UI binds.
Notice how this completely removes the need for horrendous winforms-like hacks and also removes the need for VisualTreeHelper.ComplicateMyCode() kind of things.
Copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.

Categories

Resources