Binding several ItemsControl to one ObservableCollection including visibility - wpf

In general, we can not bind multiple controls to one ObservableCollection
Is it possible to do this in the following situation?
Only one part is visible at a time
In this situation, there is a reference error twice to the same collection
How does it actually work internally? Should it not include invisible code?
<Grid Visibility="{Binding B1Visible, Converter={StaticResource BooleanToVisibilityConverter}}">
<ItemsControl ItemsSource="{Binding Elements, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<!--Another code for B1-->
</Grid>
<Grid Visibility="{Binding B2Visible, Converter={StaticResource BooleanToVisibilityConverter}}">
<ItemsControl ItemsSource="{Binding Elements, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<!--Another code for B2 -->
</Grid>

I don't know what Elements is, and I don't know where you're headed using a grid for the itemspanel of your itemscontrol.
But.
You can bind to the same observablecollection multiple times.
In the code below the two visibility properties are Boolean and the converter translates true into Visibility.Visible and false into Visibility.Collapsed.
public class MainWindowViewModel : BaseViewModel
{
private ObservableCollection<Person> people = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get { return people; }
set { people = value; RaisePropertyChanged();}
}
private bool b1Visible = true;
public bool B1Visible
{
get { return b1Visible; }
set { b1Visible = value; RaisePropertyChanged(); }
}
private bool b2Visible = true;
public bool B2Visible
{
get { return b2Visible; }
set { b2Visible = value; RaisePropertyChanged(); }
}
public MainWindowViewModel()
{
People.Add(new Person { FirstName = "Chesney", LastName = "Brown" });
People.Add(new Person { FirstName = "Gary", LastName = "Windass" });
People.Add(new Person { FirstName = "Liz", LastName = "McDonald" });
People.Add(new Person { FirstName = "Carla", LastName = "Connor" });
}
}
My markup:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<StackPanel>
<Grid Visibility="{Binding B1Visible, Converter={StaticResource BooleanToVisibilityConverter}}">
<ItemsControl ItemsSource="{Binding People}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding LastName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!--Another code for B1-->
</Grid>
<Grid Visibility="{Binding B2Visible, Converter={StaticResource BooleanToVisibilityConverter}}">
<ItemsControl ItemsSource="{Binding People}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding LastName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</StackPanel>
Both itemscontrols are visible and so I see the list of names twice.

Related

Bind ListViewItem ContextMenu MenuItem Command to ViewModel of the ListView's ItemsSource

I have a ListView of Expanders. Each Expander (representing a database table) will have items under it, in another ListView. I want to Right-Click and have an "Edit" option on the innermost items, which represent records in the corresponding database table.
There is an ICommand named 'Edit' in my MainEditorViewModel. The Datacontext in which this command resides is the same as that of the outermost ListView named "TestGroupsListView"
Here is the XAML markup for the ListView of Expanders. The outermost ListView I've named for referencing in the binding via ElementName for the MenuItem's Binding:
<ListView Name="TestGroupsListView" ItemsSource="{Binding TestGroups}" Grid.Row="1">
<ListView.ItemTemplate>
<DataTemplate>
<Expander Style="{StaticResource MaterialDesignExpander}" >
<Expander.Header>
<Grid MaxHeight="50">
<TextBlock Text="{Binding Name}"/>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Add..." Command="{Binding Add}"/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</Expander.Header>
<ListView ItemsSource="{Binding Records}" Style="{StaticResource MaterialDesignListView}" Margin="30 0 0 0">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit"
Command="{Binding ElementName=TestGroupsListView, Path=DataContext.Edit}"
CommandParameter="{Binding }"/>
</ContextMenu>
</Grid.ContextMenu>
<Button Content="{Binding RecordName}" Command="{Binding ElementName=TestGroupsListView, Path=DataContext.Edit}"/>
<!--<TextBlock Text="{Binding RecordName}" AllowDrop="True"/>-->
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I am able to bind a button in the DataTemplate to 'Edit' successfully, but when I attempt to bind the MenuItem's Command to 'Edit', nothing happens. Why might this be that the button command binding works using ElementName but the same binding in the ContextMenu doesn't?
I think it will be better to use the context menu globally for ListView and globally for each Child ListView. Ok, here is my solution:
<ListBox ItemsSource="{Binding Groups}">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Add..." Command="{Binding Add}"/>
</ContextMenu>
</ListBox.ContextMenu>
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Name}">
<ListView ItemsSource="{Binding Records}" SelectedItem="{Binding SelectedRecord}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit" Command="{Binding Edit}" IsEnabled="{Binding CanEdit}"/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And for better understanding code behind:
public class GroupsVM : ViewModelBase
{
public ICommand Add
{
get => null; //Command implementation
}
public ObservableCollection<GroupVM> Groups { get; set; } = new ObservableCollection<GroupVM>()
{
new GroupVM { Name = "First" },
new GroupVM { Name = "Second" },
new GroupVM { Name = "Third" }
};
}
public class GroupVM : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
public ICommand Edit
{
get => null; //Command implementation
}
public bool CanEdit => SelectedRecord != null;
public ObservableCollection<RecordVM> Records { get; set; } = new ObservableCollection<RecordVM>()
{
new RecordVM { Name="Record1" },
new RecordVM { Name="Record2" },
new RecordVM { Name="Record3" }
};
private RecordVM _selectedRecord = null;
public RecordVM SelectedRecord
{
get => _selectedRecord;
set
{
_selectedRecord = value;
OnPropertyChanged();
OnPropertyChanged("CanEdit");
}
}
}
public class RecordVM : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
}

ListBox within WPF TextBox - Chips

I need to add ListBox within a TextBox i.e., CHIPS
Refer the Screen shot: (Expectation)
Just Consider the View Model:
public class Person
{
private ObservableCollection<string> _personList = new ObservableCollection<string>();
public ObservableCollection<string> PersonList
{
get { return _personList; }
set
{
_personList = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("PersonList"));
}
}
private string _personStr = String.Empty;
public string PersonStr
{
get { return _personStr; }
set
{
_personStr = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("PersonStr"));
}
}
public Person()
{
PersonList.Add("IR-Punch");
PersonList.Add("Stack-Overflow");
}
public ICommand BTextCommand
{
get
{
return new DelegateCommand(AppendString);
}
}
public void AppendString()
{
PersonList.Add(PersonStr);
}
}
The working XAML Source Code:
<ItemsControl ItemsSource="{Binding PersonList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding }" TextWrapping="Wrap"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBox Text="{Binding PersonStr}" Width="160" VerticalAlignment="Center" />
<Button Command="{Binding BTextCommand}" Content="Add" />
Kindly assist me how to add ListBox within a TextBox. I take care of Sytle. I'm expecting the core idea.
Don't try to embed additional controls within the TextBox, instead embed the ItemsControl and TextBox in a stack panel as shown below:
<Border BorderThickness="1">
<ScrollViewer VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto">
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding PersonList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBox MinWidth="100" BorderBrush="Transparent" BorderThickness="0" Text="{Binding PersonStr}" />
</StackPanel>
</ScrollViewer>
</Border>
You will want to style the outer Border to look like a TextBox (pretty much just finding the correct BorderBrush) but, as requested, I've left the styling to you.
Please note, I've not actually tried this solution, just penned it as an approach that should work. Let me know if you have any problems with it.

Design-time data for ItemsControl's DataTemplate

I want to use some mock data to design my DataTemplate. How do I set a mock ObservableCollection as the ItemsSource of my ItemsControl, considering I'm using d:DataContext on it to point to a mock class containing said collection?
Here is what I have so far:
<DataTemplate x:Key="MyTemplate">
<Grid Margin="5,5,5,5">
<CheckBox Content="{Binding Name}" />
</Grid>
</DataTemplate>
<ItemsControl d:DataContext="{d:DesignInstance Type=mocks:MyViewModelMock, IsDesignTimeCreatable=True}" ItemsSource="{Binding MyMockList}" ItemTemplate="{StaticResource MyTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
public class MyViewModelMock {
public ObservableCollection<MyModel> MyMockList { get; set; }
public MyViewModelMock() {
MyMockList .Add(new MyModel() { Name = "Mock 1" });
MyMockList .Add(new MyModel() { Name = "Mock 2" });
}
}
Point you d:DataContext at a static implementation of that type and you will have your design time data context. Here is a good example http://adamprescott.net/2012/09/12/design-time-data-binding-in-wpf/

How to read all textbox values(Map to database fileds) and store them in Database in WPF - MVVM

I am new to WPF and MVVM. After a long invention i got to know how to retrieve data from database and bind it to a itemcontrol/listview/gridview.
But my problem i am not getting how to read bunch of textbox values and store as a new record in database.
Here is my sample code..
View
<ItemsControl ItemsSource="{Binding AllEmployees}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Width="100" Text="{Binding FirstName}" Margin="4"/>
<TextBox Width="100" Text="{Binding LastName}" Margin="4"/>
<TextBox Width="100" Text="{Binding Age}" Margin="4"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- For new Employee Details -->
<StackPanel>
<TextBox x:Name="FirstName"/>
<TextBox x:Name="LastName"/>
<TextBox x:Name="Age"/>
<Button Content="New" Command="{Binding NewEmployeeCommand}"/>
</StackPanel>
My cs file is
public ObservableCollection<DataAccess.Employee> AllEmployees
{
get;
private set;
}
public EmployeeListViewModel(EmployeeRepository employeeRepository)
{
if (employeeRepository == null)
{
throw new ArgumentNullException("employeeRepository");
}
_employeeRepository = employeeRepository;
this.AllEmployees = new ObservableCollection<DataAccess.Employee>
(_employeeRepository.ListAll());
}
Now how could i store a new employee Firstname, Lastname, Age in database by reading those text boxes..
How to write function for NewEmployeeCommand event to read the textboxes( mapping of textboxes to appropriate datafileds in database) and store the data in database.
Thanks a Lot !
if you're trying to use MVVM just need to:
Create your ViewModel to contain all the properties your View needs
Bind to those properties in Xaml
For example:
public class EmployeeListViewModel
{
public ObservableCollection<Employee> AllEmployees {get;private set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int? Age {get;set;}
public ICommand NewEmployeeCommand {get;set;}
//You need to connect to this method by using a Delegate/RelayCommand see link below
public void AddNewEmployee()
{
//Add your real code here to actually insert into the db
var result = InsertEmployeeIntoDatabase(FirstName,LastName,Age);
//You probably want to add this new employee to the list now ;)
AllEmployees.Add(result);
//Now you probably want to reset your fields
FirstName = null;
LastName = null;
Age = null;
}
}
Click here for an implementation of a delegate command
And then just edit your xaml like this:
<ItemsControl ItemsSource="{Binding AllEmployees}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Width="100" Text="{Binding FirstName}" Margin="4"/>
<TextBox Width="100" Text="{Binding LastName}" Margin="4"/>
<TextBox Width="100" Text="{Binding Age}" Margin="4"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- For new Employee Details -->
<StackPanel>
<TextBox Text={Binding FirstName}"/>
<TextBox Text={Binding LastName}"/>
<TextBox Text={Binding Age}"/>
<Button Content="New" Command="{Binding NewEmployeeCommand}"/>
</StackPanel>
You could pass references in the command parameter:
<StackPanel>
<TextBox x:Name="FirstName"/>
<TextBox x:Name="LastName"/>
<Button Content="New" Command="{Binding NewEmployeeCommand}">
<Button.CommandParameter>
<x:Array Type="{x:Type TextBox}">
<x:Reference Name="FirstName"/>
<x:Reference Name="LastName"/>
</x:Array>
</Button.CommandParameter>
</Button>
</StackPanel>
Depending on what kind of event you use you case the parameter and get the values:
TextBox[] textBoxes = e.Parameter as TextBox[]; //RoutedEvent
TextBox[] textBoxes = parameter as TextBox[]; //If the executed handler provides the parameter
string firstName = textBoxes[0].Text;
string lastName = textBoxes[1].Text;
//create entry; store in DB
Via binding:
<Button.CommandParameter>
<local:MyEntry FirstName="{Binding ElementName=FirstName, Path=Text}"
LastName="{Binding ElementName=LastName, Path=Text}"/>
</Button.CommandParameter>
MyEntry entry = parameter as MyEntry;
//store in DB
you dont read the Textbox values. you need a NewEmployeeViewModel and bind the TextBoxes to the properties.
EDIT:
just create a class with INotifyPropertyChanged and the Properties you need.
public class NewEmployee : INotifyPropertyChanged
{
public string FirstName
{
get{return this._firstname;}
set{this._firstname = value;
OnPropertyChanged("FirstName");}
}
//... other properties
}
xaml
<StackPanel DataContext={Binding MyNewEmployeeProperty}>
<TextBox x:Name="FirstName" Text={Binding FirstName}/>
<TextBox x:Name="LastName" Text={Binding LastName}/>
<TextBox x:Name="Age" Text={Binding Age}/>
<Button Content="New" Command="{Binding NewEmployeeCommand}"/>
</StackPanel>
I got the Correct answer.
My New entry form in xmal Should be
<StackPanel>
<TextBox Text={Binding Employee.FirstName}"/>
<TextBox Text={Binding Employee.LastName}"/>
<TextBox Text={Binding Employee.Age}"/>
<Button Content="New" Command="{Binding NewEmployeeCommand}"/>
</StackPanel>
My cs file
public class EmployeeListViewModel : INotifyPropertyChanged
{
Employee _employee;
RelayCommand _addNewEmployee;
EmployeeRepository _employeeRepository;
public Employee Employee
{
get
{
return _employee;
}
set
{
_employee = value;
OnPropertyChanged("Employee");
}
}
public void NewEmployeeCommand()
{
if(_addNewEmployee == null)
{
_addNewEmployee = new RelayCommand( param => NewEmployeeCommandExecute(),
param => NewEmployeeCommandCanExecute
);
}
}
void NewEmployeeCommandExecute()
{
_employeeRepository.Add(Employee);
_employeeRepository.Save();
}
bool NewEmployeeCommandCanExecute
{
get
{
return true;
}
}
}

WPF combox and multiple checkboxes

In XAML how would you have in a list or grid on the left side a combo box and the right side multiple check boxes in a straight line?
Let say I had a data structure like.
sudo:
// for combo
class Option
{
int key {get;set;}
string value{get;set;}
}
// for checkboxes
class Selection
{
int key {get;set;}
string value{get;set;}
bool isSelected {get;set;}
}
class Item
{
Item
{
selections = new List<Selection>();
Options = new List<Option>();
}
List<Selection> selections {get;set;}
List<Option> Options{get;set;}
}
Now this would be the item source.
List<Item> x = new List<Item>();
Item i = new Item();
i.Selections.add(blah); 25 selections
i.Options.add(blah); 3 checkboxes
x.add(i) 50 combination's.
control.itemsource = x;
What would the XAML look like. I am stuck as I quite dont get it.
Thanks...
<ListBox ItemsSource="{Binding Items}" >
<ListBox.ItemTemplate>
<DataTemplate>
<!-- This is your combobox -->
<DockPanel HorizontalAlignment="Stretch" LastChildFill="False">
<ComboBox ItemsSource="{Binding Options}" DockPanel.Dock="Left">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding value}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- This is your line of checkboxes -->
<ListBox ItemsSource="{Binding Selections}" DockPanel.Dock="Right">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding isSelected}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Resources