I'm writing an application in C# with WPF. I have a List of Objects in my code that I want to map to a Grid. The List resembles data from a 2D field. The width (x-coordinate) and height (y-coordinate) of the field can change. I'm using an ItemsControl with a UniformGrid as ItemsPanelTemplate. So far, it works.
However, now I want the user to be able to draw a line between two y-coordinates, so for example between y=6 and y=7. The intention is for the user to enter this value in a TextBox, so in this example, the user would enter "6" into the TextBox.
I tried to add a Separator or a Line to the UniformGrid, but unfortunately, that doesn't work. I did get these to work for a Grid with a defined number of columns and rows, but unfortunately, I don't have fixed columns and rows.
I don't know how to get this to work, so any help would be greatly appreciated! Thanks a lot.
See the code below for the ItemsControl:
<ItemsControl Name="Field" ItemsSource="{Binding visible, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid
Columns="{Binding cols}"
HorizontalAlignment="left"
VerticalAlignment="top"
Name="GridName">
</UniformGrid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1" Height="20">
<TextBlock
Text="{Binding Path=inputEntries.EntryNR}"
MouseDown="CellClick"
Width="50"
TextAlignment="Center"
VerticalAlignment="Center"
Background="{Binding Path=Intrial, UpdateSourceTrigger=PropertyChanged}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I misunderstood exactly what was needed. I left the other answer here because it can be adapted in a way to achieve line drawing on a uniform grid for a type of "selection" but, I really don't recommend doing it that way. I think what we really want is below. You would have to populate your Row/Holder data properly but, the entire row gets selected. You'd also be able to remove objects in the Row dynamically or add data dynamically to the "y" positions. However, you'd still have to select the entire "y" position.
First the view:
<Window.Resources>
<!-- This styling can be altered but the ControlTemplate is how we are achieving the top and bottom lines on the selected listviewitem. It still needs the contentpresenter -->
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Border x:Name="Bd" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<Grid>
<Border x:Name="TopBorder" BorderBrush="Transparent" BorderThickness="0,1,0,0" />
<Border x:Name="BottomBorder" BorderBrush="Transparent" BorderThickness="0,0,0,1" />
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="TopBorder" Property="BorderBrush" Value="Black"/>
<Setter TargetName="BottomBorder" Property="BorderBrush" Value="Black"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Bd" Property="Background" Value="LightGray"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Window.DataContext>
<local:MainWindowVM/>
</Window.DataContext>
<StackPanel>
<TextBox
Text="{Binding SelectedCoolObjectHolder.RowID}" TextChanged="TextBox_TextChanged"/>
<ListView ItemsSource="{Binding CoolObjectHolders}" SelectedItem="{Binding SelectedCoolObjectHolder}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding RowID}" FontWeight="Bold"/>
<ItemsControl ItemsSource="{Binding CoolObjects}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid
Columns="{Binding CoolObjects.Count}">
</UniformGrid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
The text changed method(It's not very robust):
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
int.TryParse(((TextBox)sender).Text, out int toTry);
var selectedRow = viewModel.CoolObjectHolders.FirstOrDefault(x => x.RowID == toTry);
viewModel.SelectedCoolObjectHolder = selectedRow ?? viewModel.CoolObjectHolders.First();
}
The ViewModel:
public MainWindowVM()
{
//Row Data
for (int i = 1; i < 5; i++)
{
var newObjectHolder = new CoolObjectHolder(i);
//Columns Data
for (int j = 1; j < 8; j++)
{
var toAdd = new CoolObject() { Name = "Value: " + j };
newObjectHolder.CoolObjects.Add(toAdd);
}
CoolObjectHolders.Add(newObjectHolder);
}
}
private ObservableCollection<CoolObjectHolder> _CoolObjectHolders = new();
public ObservableCollection<CoolObjectHolder> CoolObjectHolders
{
get
{
return _CoolObjectHolders;
}
set
{
_CoolObjectHolders = value;
OnPropertyChanged();
}
}
private CoolObjectHolder _SelectedCoolObjectHolder;
public CoolObjectHolder SelectedCoolObjectHolder
{
get
{
return _SelectedCoolObjectHolder;
}
set
{
_SelectedCoolObjectHolder = value;
OnPropertyChanged();
}
}
Finally the Data Model:
public class CoolObjectHolder : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public CoolObjectHolder(int RowID)
{
this.RowID = RowID;
}
private int _RowID;
public int RowID
{
get { return _RowID; }
set { _RowID = value; OnPropertyChanged(); }
}
private ObservableCollection<CoolObject> _CoolObjects = new();
public ObservableCollection<CoolObject> CoolObjects
{
get { return _CoolObjects; }
set { _CoolObjects = value; OnPropertyChanged(); }
}
}
public class CoolObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private string _Name = "";
public string Name
{
get { return _Name; }
set { _Name = value; OnPropertyChanged(); }
}
}