My goal is have each TabItem linked to a specific viewmodel. Furthermore, after transvering through each TabItem, the user input should not be reset. I am finding solutions for this and came across a potential solution but my testing failed me.
I have searched for answers and chose to do the following as it seems it applies the MVVM concept and it looks neat! However, I have a XAML binding error. I tried to replicate Jakob Christensen's answer provided in this link. I tried to debug and I think the issue is with the type of ObservableCollection that is created. It's an object type.
Thank you for helping!
Error
This is the XAML code for my view
<TabControl>
<TabItem DataContext="{Binding TabList[0]}" x:Name="Tab1" Header="Tab1" Margin="-2,-2,-2,2" >
<Grid>
<TextBox x:Name ="EnterNum1" Margin="300,100,300,300" Text="{Binding test1, Mode =TwoWay}"/>
<Button Name="RunBtn1" Command="{Binding Path=RunBtn1, Mode=TwoWay}" Content="RUN" HorizontalAlignment="Right" Width="180" Height="40" FontSize="18"/>
</Grid>
</TabItem>
<TabItem DataContext="{Binding TabList[1]}" x:Name="Tab2" Header="Tab2" >
<Grid>
<TextBox x:Name ="EnterNum2" Margin="300,100,300,300" Text="{Binding test2, Mode =TwoWay}" Grid.Column="1"/>
<Button Name="RunBtn2" Command="{Binding Path=RunBtn2, Mode=TwoWay}" Content="RUN" HorizontalAlignment="Right" Width="180" Height="40" FontSize="18"/>
</Grid>
</TabItem>
</TabControl>
This is the XAML.cs for my view
public ObservableCollection<object> TabList { get; set; }
public ImportData()
{
InitializeComponent();
TabList = new ObservableCollection<object>();
TabList.Add(new SampleViewModel1());
TabList.Add(new SampleViewModel2());
}
You should first set DataContext to TabControl or Window,
then you can bind the TabItem to the viewmodel(TabList) list item
XAML
<TabControl>
<TabItem DataContext="{Binding TabList[0]}" x:Name="Tab1" Header="Tab1" Margin="-2,-2,-2,2" >
<Grid>
<TextBox x:Name ="EnterNum1" Margin="300,100,300,240" Text="{Binding Test1, Mode =TwoWay}"/>
<Button Name="RunBtn1" Command="{Binding Path=RunBtn1, Mode=TwoWay}" Content="RUN" HorizontalAlignment="Right" Width="180" Height="40" FontSize="18"/>
</Grid>
</TabItem>
<TabItem DataContext="{Binding TabList[1]}" x:Name="Tab2" Header="Tab2" >
<Grid>
<TextBox x:Name ="EnterNum2" Margin="300,100,300,240" Text="{Binding Test2, Mode =TwoWay}" Grid.Column="1"/>
<Button Name="RunBtn2" Command="{Binding Path=RunBtn2, Mode=TwoWay}" Content="RUN" HorizontalAlignment="Right" Width="180" Height="40" FontSize="18"/>
</Grid>
</TabItem>
</TabControl>
XAML.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
AllViewModel allViewModel = new AllViewModel();
allViewModel.TabList = new ObservableCollection<object>();
allViewModel.TabList.Add(new SampleViewModel1());
allViewModel.TabList.Add(new SampleViewModel2());
this.DataContext = allViewModel;
}
}
public class AllViewModel : INotifyPropertyChanged
{
private ObservableCollection<object> tabList;
public ObservableCollection<object> TabList
{
get => tabList;
set { tabList = value;RaiseChange("TabList"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaiseChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class SampleViewModel1 : INotifyPropertyChanged
{
private string test1 = "test1";
private ICommand runBtn1;
public string Test1
{
get => test1;
set { test1 = value;RaiseChange("Test1"); }
}
public ICommand RunBtn1
{
get => runBtn1;
set { runBtn1 = value;RaiseChange("RunBtn1"); }
}
public SampleViewModel1()
{
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaiseChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class SampleViewModel2 : INotifyPropertyChanged
{
private string test2 = "test2";
private ICommand runBtn2;
public string Test2
{
get => test2;
set { test2 = value; RaiseChange("Test2"); }
}
public ICommand RunBtn2
{
get => runBtn2;
set { runBtn2 = value; RaiseChange("RunBtn2"); }
}
public SampleViewModel2()
{
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaiseChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
run result
Related
I'm using tabitem which contains a popup to show that data is loading from API .
The problem is the popup shows over the window, and it disappears whenever another tab is selected even though the data is still loading.
Is it possible for popup to be shown within the tab like other controls? and keep doing what it's doing whether the containing tab is selected or not?
what control do you suggest to use instead of popup if it's not the right one for this case?
Here is an example to explain:
ViewModel:
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private bool showPopUp;
public bool ShowPopUp
{
get { return showPopUp; }
set
{
showPopUp = value;
OnPropertyChanged(nameof(ShowPopUp));
}
}
}
Code behind:
public partial class MainWindow : Window
{
MainWindowViewModel model = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = model;
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
//loading data
model.ShowPopUp = true;
await Task.Delay(100000);
model.ShowPopUp = false;
}
}
XAML:
<TabControl x:Name="tc">
<TabItem x:Name="tab1" Header="Tab1">
<TabItem.Content>
<Grid>
<Popup
Name="popUp1"
StaysOpen="True"
IsOpen="{Binding ShowPopUp, Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}"
AllowDrop="True" Placement="Center"
Height="90" Width="135">
<Grid Background="LightGray">
<TextBlock Text="Loading ..." Margin="32" FontSize="16" />
</Grid>
</Popup>
<Button Height="30" Width="100" Content="Show popup" Click="Button_Click"/>
</Grid>
</TabItem.Content>
</TabItem>
<TabItem
x:Name="tab2"
Header="Tab2">
</TabItem>
</TabControl>
So, I tested many answers I found on different topics, but still my WPF app does not update binded data. When I set all Properties before Initializing MainWindow Data are displayed correctly, but I need to select directory, date, etc. before loading the data. Tried to change DataContext in code behind, but IT doesn't work. All the classes used as VieModels have implemented INotifyPropertyChanged interface (but the PropertyChanged values is always null). I'm out of ideas now...
This is XAML code:
<Window x:Class="WpfDataBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfDataBinding"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="800" Name="Logs">
<Window.DataContext>
<local:CustomDataContexts />
</Window.DataContext>
<Grid Name="Logi">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<local:CustomButton Grid.Column="0" Grid.Row="1" Margin="5" Height="35" Width="100" x:Name="Choose" Text="Wybierz:" ImageSource="Resources/choose.png" Click="CustomButton_Click" />
<local:CustomButton Grid.Column="0" Grid.Row="2" Margin="5" Height="35" Width="100" x:Name="Load" Text="ZaĆaduj:" ImageSource="Resources/load.png" Click="CustomButton_Click" />
<local:CustomButton Grid.Column="0" Grid.Row="3" Margin="5" Height="35" Width="100" x:Name="Search" Text="Szukaj:" ImageSource="Resources/search.png" Click="CustomButton_Click" />
<local:CustomButton Grid.Column="3" Grid.Row="2" Margin="5" Height="80" Width="100" x:Name="Next" Grid.RowSpan="2" Text="Dalej:" ImageSource="Resources/next.png" Click="CustomButton_Click" />
<TabControl DataContext="{Binding TextViewModel}" x:Name="tabControl" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding TxtView.Tabs, ElementName=Logs, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate >
<!-- this is the body of the TabItem template-->
<DataTemplate>
<ListView ItemsSource="{Binding EntryViewModels}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Entry.Tag}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<TabControl DataContext="{Binding SingleNode}" x:Name="tabControl2" Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Code-behind looks like this:
public CustomDataContexts DataContexts { get; set; }
public string Path { get; set; }
public Files Files { get; set; }
public MainWindow()
{
/*Path = #"C:\Users\Slawek\Desktop\Logs\logi";
Files = new Files(Path);
Files.NarrowFiles(false, DateTime.MinValue);
var entry = new EntryCollection(Files.SelectedFiles[1], Files.SelectedFiles, null);
TxtView = new TxtViewModel(new List<TxtTabItem>(new[] { new TxtTabItem(entry) }));*/
InitializeComponent();
}
private void CustomButton_Click(object sender, RoutedEventArgs e)
{
var fe = (FrameworkElement) sender;
switch (fe.Name)
{
case "Choose":
var g = new FolderBrowserDialog();
if (g.ShowDialog() == System.Windows.Forms.DialogResult.OK)
Path = g.SelectedPath;
break;
case "Load":
DataContexts = new CustomDataContexts();
Files = new Files(Path);
Files.NarrowFiles(false, DateTime.MinValue);
var entry = new EntryCollection(Files.SelectedFiles[1], Files.SelectedFiles, null);
DataContexts.TextViewModel = new TxtViewModel(new List<TxtTabItem>(new[] { new TxtTabItem(entry) }));
break;
case "Search":
break;
case "Next":
break;
}
}
CustomDataContexts class:
public class CustomDataContexts : INotifyPropertyChanged
{
private TxtViewModel textViewModel;
public XmlViewModel SingleNode { get; set; }
public TxtViewModel TextViewModel
{
get { return textViewModel; }
set { OnPropertyChanged("TextViewModel"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
EntryViewModel:
public class EntryViewModel : INotifyPropertyChanged
{
private SingleEntry entry;
public SingleEntry Entry
{
get { return entry; }
set
{
entry = value;
OnPropertyChanged("Entry");
}
}
public EntryViewModel(SingleEntry entry)
{
Entry = entry;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
TxtViewModel:
public class TxtViewModel :INotifyPropertyChanged
{
private ObservableCollection <TxtTabItem> tabs;
public ObservableCollection<TxtTabItem> Tabs
{
get { return tabs; }
set
{
tabs = value;
OnPropertyChanged("Tabs");
}
}
public TxtViewModel(List<TxtTabItem> items)
{
Tabs = new ObservableCollection <TxtTabItem>();
foreach (var txtTabItem in items)
{
Tabs.Add(txtTabItem);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And finally TxtTabItem class:
public class TxtTabItem : INotifyPropertyChanged
{
public string Header { get; set; }
public ObservableCollection<EntryViewModel> EntryViewModels { get; set; }
public TxtTabItem(EntryCollection collection)
{
Header = collection.Date.ToShortDateString();
EntryViewModels = new ObservableCollection <EntryViewModel>();
foreach (var entry in collection.Entries)
{
EntryViewModels.Add(new EntryViewModel(entry));
}
OnPropertyChanged("EntryViewModels");
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I will be very grateful for any suggestions of how to make this code work. I'm pretty new to WPF and still don't know it well enough.
The framework is there, but generally to intialize controls to changes one binds to the ItemsSource property instead of directly to the DataContext.
In general the DataContext is something that provides information about the current item, but when it is null the parent's data context is used up until the page's data context.
The reason for that is a data context will hold a large class of properties and the binding will look (reflect/reflection) at the DataContext for that named property. So by using an ItemsSource one can bind to a specific set of items, while still having a DataContext full of information to allow other items on the control to be bound to other specific properties.
Under MVVM where the VM or view model is that class of properties as mentioned, set the page's data context to that VM and then bind to individual properties on the different controls you have from the page's datacontext.
so
<TabControl DataContext="{Binding TextViewModel}" x:Name="tabControl"
becomes
<TabControl ItemsSource="{Binding TextViewModel}" x:Name="tabControl"
once you set the page's data context to TxtViewModel.
I provide a binding/VM example on my blog article Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding.
I have the a tabcontrol in my MainWindow. The default/first tab of the tabcontrol is the home user control. In the home page I have a button that can add further tabs.
MainWindow.xaml:
<Window.Resources>
<DataTemplate x:Key="ClosableTabItemTemplate">
<Button Content="X" Cursor="Hand" DockPanel.Dock="Right" Focusable="False"
FontFamily="Courier" FontSize="9" FontWeight="Bold" Margin="0,1,0,0" Padding="0"
VerticalContentAlignment="Bottom" Width="16" Height="16"/>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl Name="tabMain" ItemsSource="{Binding TabItems,UpdateSourceTrigger=PropertyChanged}" />
</Grid>
In my view model I have the add functionality where a new tab has been added. I need the close button for all these newly added tabs.
public MainViewModel()
{
try
{
Home Item2 = new Home();
TabItems.Add(new TabItem() { Header = "Home", Content = Item2 });
}
catch(Exception ex)
{
MessageBox.Show("Exception "+ex);
}
//Function to add new tabs.
public void AddNewTabs()
{
ChildWindow childContent = new ChildWindow();
TabItem item = new TabItem() { Header = "New Tab", Content = childContent};
item.MouseDoubleClick += new MouseButtonEventHandler(tab_MouseDoubleClick);
TabItems.Add(item);
}
Right now new tabs are being added but without the close button. I have tried giving
item.HeaderTemplate = FindResource("ClosableTabItemTemplate") as DataTemplate;
But it shows error.
Any help would be appreciated.
Thanks in advance.
You could have a look at Dragablz which does this and more.
Disclaimer: this is my library, but it's open source, so enjoy.
You view model must not interact directly with the view in order to respect the Mvvm pattern, meaning that you need to use commands instead of Events, don't use any view related control in your View model logic ..
here a cleaner way to achieve what you're looking for :
First In the view use the TabControl ContentTemplate and ItemTemplate Instead :
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Button Content="Add new tab" Command="{Binding AddNewTabCommand}"></Button>
<TabControl Grid.Row="1" Name="TabMain" ItemsSource="{Binding TabItems,UpdateSourceTrigger=PropertyChanged}" >
<TabControl.ItemTemplate>
<DataTemplate >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Header}"/>
<Button Content="X" Cursor="Hand" DockPanel.Dock="Right" Focusable="False"
FontFamily="Courier" FontSize="9" FontWeight="Bold" Margin="0,1,0,0" Padding="0"
VerticalContentAlignment="Bottom" Width="16" Height="16" Command="{Binding DataContext.CloseTabCommand,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" CommandParameter="{Binding ElementName=TabMain,Path=SelectedItem}"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}" VerticalAlignment="Center" HorizontalAlignment="Center"></TextBlock>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
Second, In the ViewModel Create a class TabItem that will hold a tab Content and header (customize it as needed), you may want to implement the INotifyPropertyChanged interface if the class reflect any changes to the view,
Third, define the commands to add and romove a TabItem from TabItems ObservableCollection,
here the viewModel Code :
public class TabItem
{
public String Header { get; set; }
public String Content { get; set; }
}
public class MainViewModel : INotifyPropertyChanged
{
private ObservableCollection<TabItem> _tabItems;
public ObservableCollection<TabItem> TabItems
{
get
{
return _tabItems;
}
set
{
if (_tabItems == value)
{
return;
}
_tabItems = value;
OnPropertyChanged();
}
}
private RelayCommand _addNewTabCommand;
public RelayCommand AddNewTabCommand
{
get
{
return _addNewTabCommand
?? (_addNewTabCommand = new RelayCommand(
() =>
{
TabItems.Add(new TabItem()
{
Header = "NewTab",
Content = "NewContent"
});
}));
}
}
private RelayCommand<TabItem> _closeTabCommand;
public RelayCommand<TabItem> CloseTabCommand
{
get
{
return _closeTabCommand
?? (_closeTabCommand = new RelayCommand<TabItem>(
(t) =>
{
TabItems.Remove(t);
}));
}
}
public MainViewModel()
{
TabItems = new ObservableCollection<TabItem>()
{
new TabItem()
{
Header = "Home",
Content = "Home Content"
},
new TabItem()
{
Header = "Header1",
Content = "Content1"
}
};
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
the output :
Ps: My MainWindow View DataContext is set to MainWindowViewModel, and tha's why i am using AncestorType to find the CloseTabCommand
So, I have a TabControl binded to a list of projects (each tab is a one project) - that works fine. The content of each tab is a DataGrid with a list of project's employees - that works fine as well. Now, I want to show some information on employee currently selected on DataGrid. Here's some code:
MainWindow.xaml file:
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
<DataTemplate x:Key="ContentTemplate">
<DataGrid ItemsSource="{Binding Employees}" SelectedItem="{Binding SelectedEmployee, Mode=TwoWay}" SelectionMode="Extended" SelectionUnit="FullRow" Name="employeesList">
</DataGrid>
</DataTemplate>
</Window.Resources>
and later, I want to test this binding by simply writing it in label:
<Label Name="emp" Content="{Binding SelectedEmployee}"></Label>
and MainWindowViewModel:
public Employee SelectedEmployee { get { return selectedEmployee; }
set
{
if (selectedEmployee != value)
{
selectedEmployee = value;
NotifyPropertyChanged("SelectedEmployee");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
I am kind of a newbie to WPF, I've read some tips but they don't help. The label 'emp' does not show anything. What am I missing?
You are not notifying that your property has changed, Try this
public Employee SelectedEmployee
{
get { return selectedEmployee; }
set
{
if (selectedEmployee != value)
{
selectedEmployee = value;
LastName = value;
NotifyPropertyChanged("SelectedEmployee"); //NotifyPropertyChanged("SelectedItem");
}
}
}
Test:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication6"
Title="MainWindow" Height="350" Width="763" Name="UI" >
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding ElementName=UI,Path=Employees}" SelectedItem="{Binding ElementName=UI,Path=SelectedEmployee}" SelectionMode="Extended" SelectionUnit="FullRow" Name="employeesList" Margin="0,41,0,0" />
<Label Content="{Binding ElementName=UI,Path=SelectedEmployee.Name}" Height="28" HorizontalAlignment="Left" Name="label1" VerticalAlignment="Top" Width="288" />
<Label Content="{Binding ElementName=employeesList,Path=SelectedItem.Name}" Height="28" HorizontalAlignment="Left" Name="label2" VerticalAlignment="Top" Width="288" Margin="294,0,0,0" />
</Grid>
</Window>
Code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Employee> _employees = new ObservableCollection<Employee>();
private Employee _selectedEmployee;
public MainWindow()
{
InitializeComponent();
Employees.Add(new Employee { Name = "sa_ddam213" });
}
public ObservableCollection<Employee> Employees
{
get { return _employees; }
set { _employees = value; }
}
public Employee SelectedEmployee
{
get { return _selectedEmployee; }
set { _selectedEmployee = value; NotifyPropertyChanged("SelectedEmployee"); }
}
/// <summary>
/// Notifies the property changed.
/// </summary>
/// <param name="info">The info.</param>
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
public class Employee
{
public string Name { get; set; }
}
This seems to work as expected, or am I missing something?
I was working on dynamic generation of labels, buttons and Textbox in my WPF application. Well I was successful in dynamically creating them but I am facing one major issue in it.
Xaml:
<ListBox x:Name="myViewChannelList" HorizontalAlignment="Stretch" Height="Auto" ItemsSource="{Binding}" Margin="0" VerticalAlignment="Stretch" Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Path=ChanelName}" Margin="50,20,0,0"></Label>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Path=VoltageText}" Height="25" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" />
<Button Grid.Column="1" Content="Set" Height="25" Command="{Binding ElementName=myViewChannelList, Path=DataContext.SetCommand}" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" ></Button>
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Model Class:
private string _ChanelName = "";
public String ChanelName
{
get
{
return _ChanelName;
}
set
{
if (value != _ChanelName)
{
_ChanelName = value;
OnPropertyChanged("ChanelName");
}
}
}
// Constructor
public VoltageModel(string ChanelName)
{
this.ChanelName = ChanelName;
}
public override string ToString()
{
return _ChanelName;
}
ViewModel Class:
class ChannelList : ObservableCollection<VoltageModel>, INotifyPropertyChanged
{
private string _VoltageText;
public string VoltageText
{
get { return _VoltageText; }
set
{
_VoltageText = value;
OnPropertyChanged("VoltageText");
}
}
// Method gets called when Set Button Is Clicked
public void SetCommandExecuted()
{
string val = VoltageText;
}
//Notify Property Changed members are present
}
Xaml.cs Class:
ChannelList myChanels = new ChannelList();
public VoltageView() // Constructor
{
InitializeComponent();
myChanels.Add(new VoltageModel("VDD__Main"));
myChanels.Add(new VoltageModel("VDD__IO__AUD"));
myChanels.Add(new VoltageModel("VDD__CODEC__AUD"));
myViewChannelList.DataContext = myChanels;
}
This gives me 3 Labels(Content as above), 3 textboxes and 3 buttons when I run the application.
Now when I enter the value inside the textbox it shows null on button click when I put a breakpoint in SetCommandExecuted(). Most importantly any of the 4 button I click generates the event. I want the first textbox and first button to be in sync(bind), 2nd textbx and 2nd button to be in sync and so on. Basically each control must be in sync with the other control in a row. It should not effect the other rows. Is it possible???
Here is the solution to your question. As general practice you want to avoid all logic, building your data, etc. in the code behind. All the business logic should be in the view model which will make it easier to unit test.
Here is the view
.xaml
<StackPanel>
<ListBox HorizontalAlignment="Stretch"
Height="Auto"
ItemsSource="{Binding VoltageCollection}"
VerticalAlignment="Stretch"
Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Width="100"
Content="{Binding ChannelName}" />
<TextBox Width="100"
Text="{Binding VoltageText}" />
<Button Margin="10,0,0,0"
Content="Set"
Command="{Binding VoltageCommand}"
CommandParameter="{Binding VoltageText}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
Here is the code behind
.xaml.cs
private ChannelListViewModel m_voltageViewModel;
public MainWindow()
{
InitializeComponent();
m_voltageViewModel = new ChannelListViewModel();
m_voltageViewModel.Initialize();
DataContext = m_voltageViewModel;
}
Here is the Model: VoltageModel
public class VoltageModel : INotifyPropertyChanged
{
public string ChannelName { get; set; }
private string m_voltageText;
public string VoltageText
{
get { return m_voltageText; }
set
{
m_voltageText = value;
OnPropertyChanged("VoltageText");
}
}
public ICommand VoltageCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here is the ViewModel: ChannelListViewModel
public class ChannelListViewModel
{
private ICommand m_voltageCommand;
public ChannelListViewModel()
{
m_voltageCommand = new DelegateCommand(x => SetCommandExecute(x));
}
public void Initialize()
{
VoltageCollection = new ObservableCollection<VoltageModel> { new VoltageModel() { ChannelName = "VDD__Main", VoltageText = String.Empty, VoltageCommand = m_voltageCommand },
new VoltageModel() { ChannelName = "VDD__IO__AUD", VoltageText = String.Empty, VoltageCommand = m_voltageCommand },
new VoltageModel() { ChannelName = "VDD__CODEC__AUD", VoltageText = String.Empty, VoltageCommand = m_voltageCommand }};
}
public ObservableCollection<VoltageModel> VoltageCollection { get; set; }
public void SetCommandExecute(object voltageText)
{
Debug.WriteLine(voltageText);
}
}
Finally simple DelegateCommand class DelegateCommand
public class DelegateCommand : ICommand
{
Action<object> m_executeDelegate;
public DelegateCommand(Action<object> executeDelegate)
{
m_executeDelegate = executeDelegate;
}
public void Execute(object parameter)
{
m_executeDelegate(parameter);
}
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
}
i didn't get much into what was wrong since i recognized 2 things that were generally very wrong ,and they might be the problem for unexpected behavior on your part .
the first : your DataTemplate places your control one on top of the other .
fix :
<DataTemplate >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Path=ChanelName}" />
<TextBox Grid.Column="1" Text="{Binding Path=VoltageText}" />
<Button Grid.Column="2" Command="{Binding ElementName=myViewChannelList, Path=DataContext.SetCommand}" />
</Grid>
</DataTemplate>
the second : your Properties are set after PropertyChanged event was risen so they would not be updated until the next time you input a value.
fix :
private T _property;
public T Property
{
get { return _property; }
set
{
_property = value;
OnPropertyChanged("Property");
}
}
make these fixes and edit your post if you still have issues post a comment under my answer.