I am making a kind of form, and I want the user to be able to enter a dynamic number of string inputs. The idea is that a single TextBox
will be displayed, and once the user has filled that out, they will be able to add another one, and repeat the process as they keep filling the text boxes. Once there are multiple, they can remove any text box if they no longer want it.
This is accomplished with a ListBox
containing a custom DataTemplate
which is a DockPanel
with a TextBox
and two Button
s.
XAML:
<ListBox ItemsSource="{Binding ExcludedPaths}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel LastChildFill="True">
<Button DockPanel.Dock="Right"
Content="+"
Command="{Binding DataContext.AddExcludedPathCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"
Visibility="{Binding AddButtonVisibility}" />
<Button DockPanel.Dock="Right"
Content="-"
Command="{Binding DataContext.RemoveExcludedPathCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}" />
<TextBox DockPanel.Dock="Left"
Text="{Binding Path,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch" />
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ExcludedPath:
public class ExcludedPath : INotifyPropertyChanged
{
private Visibility _addButtonVisibility;
private string _path;
public Visibility AddButtonVisibility
{
get
{
return _addButtonVisibility;
}
set
{
_addButtonVisibility = value;
NotifyPropertyChanged();
}
}
public string Path
{
get
{
return _path;
}
set
{
_path = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(
[CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(
this,
new PropertyChangedEventArgs(propertyName));
}
}
ViewModel (Relevant Portions):
internal ViewModel()
{
SetUpProperties();
}
public ICommand AddExcludedPathCommand =>
new RelayCommand(AddExcludedPath, CanAddExcludedPath);
public ObservableCollection<ExcludedPath> ExcludedPaths { get;
private set; }
public ICommand RemoveExcludedPathCommand =>
new RelayCommand<ExcludedPath>(
RemoveExcludedPath, CanRemoveExcludedPath);
private void AddExcludedPath()
{
ExcludedPaths.Add(new ExcludedPath());
}
private bool CanAddExcludedPath()
{
return ExcludedPaths.All(x => !x.Path.IsNullOrWhiteSpace());
}
private bool CanRemoveExcludedPath(ExcludedPath excludedPath = null)
{
return ExcludedPaths.Count > 1;
}
private void OnExcludedPathsChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
foreach (var excludedPath in ExcludedPaths)
{
excludedPath.AddButtonVisibility = Visibility.Hidden;
}
ExcludedPaths.Last().AddButtonVisibility = Visibility.Visible;
}
private void RemoveExcludedPath(ExcludedPath excludedPath)
{
ExcludedPaths.Remove(excludedPath);
}
private void SetUpProperties()
{
ExcludedPaths = new ObservableCollection<ExcludedPath>();
ExcludedPaths.CollectionChanged += OnExcludedPathsChanged;
ExcludedPaths.Add(new ExcludedPath());
}