Interactive dynamical wpf grid - c#

I have a collection which I want to place on a grid. The collections items have properties that refer to where on the gird the item should be placed, and how many columns and rows the item should span. So the item looks sort of like this:
public class Item
{
public int FromRow { get; set; }
public int SpanRow { get; set; }
public int FromColumn { get; set; }
public int SpanColumn { get; set; }
}
I want the grid to generate the right number of columns and rows dynamically for the collection, and display the collection trough data templates for each item in it. I believe I have solved this part of the problem like so:
<ItemsControl ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding ItemCollection">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid x:Name="Grid"
ShowGridLines="True"
dataHelper:DynamicGrid.RowCount="7"
dataHelper:DynamicGrid.ColumnCount="{Binding ColumnCount}"
dataHelper:DynamicGrid.ColumnWidth="{Binding ColumnWidth}">
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Style.Setters>
<Setter Property="Grid.Row"
Value="{Binding FromRow}" />
<Setter Property="Grid.Column"
Value="{Binding FromColumn}" />
<Setter Property="Grid.RowSpan"
Value="{Binding SpanRow}" />
<Setter Property="Grid.ColumnSpan"
Value="{Binding SpanColumn}" />
</Style.Setters>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
As you can see I am using attached properties for the dynamic grid part. But there are two problems i cant figure out.
First problem: I want to enable drag and drop on the items populating the grid so that I can manipulate the items position on the grid. Now I need the mouse position where the drop is supposed to accure. I searched around and found this:
private void OnMouseMove(object sender, MouseEventArgs e)
{
var element = (UIElement)e.Source;
int c = Grid.GetColumn(element);
int r = Grid.GetRow(element);
}
Which means that the grid must have UIElements in every cell, but the ItemsControl wont allow me to add anything to the grid which is understandable.
Second problem: I want the rows and columns to have iterative headers. What I meen by that is that column one has '1' in its first cell, column two has '2' in its first cell, and so on. The same goes for rows.
I hope I have explained my problems so you guys understand them. Any input would be much appreciated. And if you know a beter way to do what I have explained, other tools than a normal Grid in a ItemsControl, please share! :D
Thanks in advance!

Related

Changing specific wpf datagrid row color using c#

I have a requirement to change color of specific data grid row at run time
I am setting row background color inside Loading Row event of data grid
private void MessagesDataGrid_LoadingRow(objects , DataGridRowEventArgs e)
{
var v = e.Row.Item.ToString();
int i = e.Row.GetIndex();
if (IoStatusViewModel.HighlightSelected == true )
{
e.Row.Focusable = true;
e.Row.Background = Brushes.Red;
if (v.Contains("MCP :"))
{
DisplayLogs = IoStatusViewModel.ChangeMcpLog(v);
e.Row.Item = DisplayLogs;
}
}
else
{
if (v.Contains("MCP :"))
{
DisplayLogs = IoStatusViewModel.ChangeMcpLog(v);
e.Row.Item = DisplayLogs;
}
}
}
This code is working fine as data grid loads but after some times the color of each rows in data grid starts changing and as time passes whole grid becomes red
I would do it within the class object you are binding against in conjunction with a style for the grid. First, your data being presented in the grid. How/where is that coming from. Is it some sort of List<> or ObservableCollect<> of items. Example
var yourBoundProperty = new List<SomeClass>();
… populate however you do.
public class SomeClass
{
public string SomeProp {get; set;}
public string YourMCPField {get; set;}
// make a SPECIAL FIELD... could be boolean, number setting, whatever flag
// but in this case, I just have boolean
public bool FieldContainsMCP { get { return YourMCPFieldContains( "MCP :"); }}
}
Now, in your Xaml… assuming in a Window declaration.
<Window … >
<Window.Resources>
<Style TargetType="{x:Type DataGridCell}" x:Key="MyColorTriggers">
<Style.Triggers>
<DataTrigger Binding="{Binding FieldContainsMCP}" Value="True">
<Setter Property="Background" Value="Red" />
<Setter Property="ExampleAnyOtherProperty" Value="someOtherValue" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<DataGrid … other settings you have
CellStyle="{StaticResource MyColorTriggers}" >
.. rest of your data column declarations
</DataGrid>
</Window>
This way the actual data source is the flag basis which is applied to the CellStyle triggering regardless of where you may be scrolling through records.

Hide empty group in WPF ListBox

I'm trying to further customize build-in capability of WPF ListBox for showing items in groups.
In short, I want to hide Group's container (and Group's title altogether) if all items inside group are collapsed (Visibility property).
First, I have very simple class City that represent single Item. This class include Shown property. Inside ItemContainerStyle I simply have DataTrigger that set Visibility to Collapsed if value of this property is False.
class City : INotifyPropertyChanged
{
private bool m_Shown = true;
public string Name { get; set; }
public string Country { get; set; }
public bool Shown
{
get
{
return m_Shown;
}
set
{
m_Shown = value;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Shown"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
This is how I add sample cities, add Group description and all work fine.
m_cities = new List<City>
{
new City() { Name = "Berlin", Country = "Germany" },
new City() { Name = "Milano", Country = "Italy" },
new City() { Name = "Frankfurt", Country = "Germany" },
new City() { Name = "Rome", Country = "Italy" }
};
ICollectionView view = CollectionViewSource.GetDefaultView(m_cities);
view.GroupDescriptions.Add(new PropertyGroupDescription("Country"));
Cities = view; // <-- Binds to ItemsSource of ListBox
I tried in several ways to automatically hide Group if there are no more items visible in it (all are collapsed), but all without luck.
One way is to repeat last 3 lines in code above and this works, but I noticed slowdown with this method and listbox must work fast for user.
Bellow is one of my examples and this actually worked for hiding, but I can't bring group to be visible anymore after that. I tried with converters and similar, but I can't get group visible again.
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Style.Triggers>
<Trigger Property="ActualHeight" Value="20">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="MinHeight" Value="20"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<TextBlock Text="{Binding Path=Name}"/>
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
Thanks for any help.
A bit (!) late with this, but hopefully it might help someone else in the future.
Inside the control template of most (all?) GroupItem styles is an ItemsPresenter that is used to host and display the child items that belong to the group. It stands to reason that, if all of the child items are collapsed, this ItemsPresenter will have a height of zero.
Therefore, you can add a trigger to the control template based on this condition, and set the Visibility of the whole group item accordingly. A normal property trigger doesn't seem to work, but a data trigger will. Something like this:
<ControlTemplate>
<StackPanel x:Name="Root">
...
<ItemsPresenter x:Name="ItemsPresenter" />
...
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ActualHeight, ElementName=ItemsPresenter}" Value="0">
<Setter TargetName="Root" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
You will need to name the root element of the control template (in this example it's a StackPanel element I've named "Root") and also the ItemsPresenter element (I've just called it "ItemsPresenter"). Obviously the root element might be a different type, and you can use whichever names you like.
You were on the right track, but you needed to bind to the ActualHeight of the ItemsPresenter, and it needed to be a data trigger not a normal property trigger.

How can I make a multi-colored segmented progress bar in wpf?

I'm trying to create a UserControl that acts as a sort of segmented progress bar. Input would be a collection of objects, each object would have a category, a duration property, and status property. The UserControl should stretch the width and height of the parent control. Each item in the collection should represent a segment of the progress bar; color of the segment is related to the status, the width of the segment is related to the duration, and the text overlaid on the segment would be related to the category or something.
Example custom progress bar:
The text might be the collection item's ID, the top segment color would be related to status, the bottom color would be related to the category, and the width related to the duration.
Some of the options I've considered:
Make a stackpanel and somehow define each items width and wrap the whole thing in a viewbox to make it stretch the height and width. How could I control the text size, how do I make the content fit the height, how do I bind a stackpanel to a collection?
Make an attached property for a grid control that would dynamically create columns and map the collection items to the grids. Seems like a lot of work and I'm hoping theres a simpler solution since my requirements are pretty specific.
Maybe theres a way to override a uniform grid to make it non-uniform?
Maybe I should just go all code-behind and draw rectangles by iterating through my collection?
Either way, I am crossing my fingers that somebody might know a simple solution to my problem.
Here is a full working proposition of solution to the custom progress bar.
Code is here : http://1drv.ms/1QmAVuZ
1 . If all the steps are not the same width, I prefer to use Grid with columns and different widths
The columns are built dynamically based upon following class :
public class StepItem
{
public int Length { get; set; }
public int Index { get; set; }
public String Label { get; set; }
public Brush Brush { get; set; }
}
2. I chose to implement a CustomControl and inherit of ItemsControl
CustomControl because I don't want to take care of implementing of the parts of the template of the Progressbar.
ItemsControl because :
-I want to provide to ItemsSource property a collection of StepItems
-ItemsControl can have some DataTemplate as template for each item
-ItemsControl can have any Panel like Grid as template presenting the collection of items
3. The component has template in Generic.xaml
-layoutGrid wil have the "continuous rainbow"
-overlayGrid will be displayed partially over the steps depending on progression or totally over (if no progress)
-ItemsPresenter will present the collection of DataTemplates corresponding to each StepItem
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ProgressItemsControl}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid x:Name="layoutGrid">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<Grid x:Name="overlayGrid" Width="100" HorizontalAlignment="Right" Background="White"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
4. Customisation of the ItemsPanel to use a Grid (instead of vertical layout)
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate >
<Grid x:Name="stepsGrid" IsItemsHost="True" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
5. In code behind of components, setting of the column width
int i = 0;
foreach (StepItem stepItem in ItemsSource)
{
total += stepItem.Length;
var columnDefinition = new ColumnDefinition() { Width = new GridLength(stepItem.Length, GridUnitType.Star) };
stepsGrid.ColumnDefinitions.Add(columnDefinition);
Grid.SetColumn(stepsGrid.Children[i], stepItem.Index);
i++;
}
6. Code behind for declaring Dependency properties that can be monitored
(excerpt)
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(int), typeof(ProgressItemsControl), new PropertyMetadata(0));
7. Usage of the component
<local:CustomProgressBar
x:Name="customProgressBar1"
HorizontalAlignment="Left" Height="50" Margin="32,49,0,0"
VerticalAlignment="Top" Width="379"/>
8. Feeding the component with data
private List<StepItem> stepItems = new List<StepItem>{
new StepItem{
Index=0,
Label="Step1",
Length=20,
Brush = new SolidColorBrush(Color.FromArgb(255,255,0,0)),
new StepItem{
Index=4,
Label="Step5",
Length=25,
Brush = new SolidColorBrush(Color.FromArgb(255,0,128,0)),
},
};
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
progressItemsControl1.ItemsSource = stepItems;
}
Regards

Hiding expander when all content is collapsed

I have A WPF Datagrid that has a Collection View Source with 3 levels of grouping on it.
I have styled the datagrid to use 3 expanders such that it looks like this:
Level 1 Expander
<content>
Level 2 Expander
<content>
Level 3 Expander
<content>
Level 2 and Level 1 are just title of the groups
I have a second control that allows the user to show and hide level 3 items which works by binding the Level 3 expander to a Boolean "IsVisible" property in the object behind.
<!-- Style for groups under the top level. this is the style for how a sample is displayed -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<!-- The parent control that determines whether or not an item needs to be displayed. This holds all of the sub controls displayed for a sample -->
<Expander Margin="2"
Background="{Binding Path=Name,
Converter={StaticResource SampleTypeToColourConverter}}"
IsExpanded="True"
Visibility="{Binding Path=Items[0].IsVisibleInMainScreen,
Converter={StaticResource BoolToVisibilityConverter}}">
This approach works fantasically well.
HOWEVER
If the user deselects all items in a level 3 expander, the Level 2 expander header still displays meaning that valuable real estate is used up showing the header of a group with no visible data.
What I would like is a way to bind the visibility of the level 2 expander to its child controls and say "If all children are visible then show the expander, otherwise collapse it"
Is this possible?
I found a rather simple and clean way, yet not perfect, to achieve your goal. This should do the trick if hou don't have too much groups.
I've just added this trigger to the GroupItem ControlTemplate :
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=IP, Path=ActualHeight}" Value="0">
<Setter Property="Visibility" Value="Hidden"/>
<Setter Property="Height" Value="1"/>
</DataTrigger>
</ControlTemplate.Triggers>
When the ItemsPresenter (IP) ActualSize drops to zero, it Will almost collapse the header.
Why almost ?
When the control gets initialized and before the binding occurs, the ItemPresenter ActualHeight is 0 and when Visibility is set to Collapsed, the ItemPresenter doesn't get rendered at all.
Using Visibility.Hidden allows the ItemsPresenter to go to the render phase and be mesured.
I succedeed to drop Height to .4 px but I suspect this to be device dependant.
Assuming that you are using an MVVM sort of style, you could bind instead to a property of your group object that returns false if all of the children are invisible:
public bool AreChildrenVisible { get { return _children.Any(x=>x.IsVisibleInMainScreen); } }
Alternatively, pass the collection of Items through a Converter class to return Visibility depending on the aggregate status of all the subItems in the group.
This isn't a direct answer as you would have to implement it specifically for your needs but previously I have used a an override of the Grid Control to create dynamic grid allocation of members, if there are no visible members it then hides the parent group box.
public class DynamicLayoutGrid : Grid
{
protected override void OnInitialized(EventArgs e)
{
//Hook up the loaded event (this is used because it fires after the visibility binding has occurred)
this.Loaded += new RoutedEventHandler(DynamicLayoutGrid_Loaded);
base.OnInitialized(e);
}
void DynamicLayoutGrid_Loaded(object sender, RoutedEventArgs e)
{
int numberOfColumns = ColumnDefinitions.Count;
int columnSpan = 0;
int rowNum = 0;
int columnNum = 0;
int visibleCount = 0;
foreach (UIElement child in Children)
{
//We only want to layout visible items in the grid
if (child.Visibility != Visibility.Visible)
{
continue;
}
else
{
visibleCount++;
}
//Get the column span of the element if it is not in column 0 as we might need to take this into account
columnSpan = Grid.GetColumnSpan(child);
//set the Grid row of the element
Grid.SetRow(child, rowNum);
//set the grid column of the element (and shift it along if the previous element on this row had a rowspan greater than 0
Grid.SetColumn(child, columnNum);
//If there isn't any columnspan then just move to the next column normally
if (columnSpan == 0)
{
columnSpan = 1;
}
//Move to the next available column
columnNum += columnSpan;
//Move to the next row and start the columns again
if (columnNum >= numberOfColumns)
{
rowNum++;
columnNum = 0;
}
}
if (visibleCount == 0)
{
if (this.Parent.GetType() == typeof(GroupBox))
{
(this.Parent as GroupBox).Visibility = Visibility.Collapsed;
}
}
}
}
Use IMultiValueConverter implementation to convert items to visibility.
If all items IsVisibleInMainScreen property return true the converter will return visible else hidden.
Use the converter in the same place U used to convert the first item in original example

Listbox Not Tracking User Input

I've got a ListBox that is displaying a dynamic number of TextBoxes. The user will enter text into these boxes. When the Submit button is clicked, I need to be able to access the text the user has input, should be at ListBox.Items, like so:
//Called on Submit button click
private void SaveAndSubmit(object sender, ExecutedRoutedEventArgs e)
{
var bounds = MyListBox.Items;
}
But MyListBox.Items doesn't change after I initially set the ItemsSource, here:
//Field declaration
//Bounds is containing a group of strings that represent the boundaries
//for a contour plot. The min/max values are stored at the front and back
//of the group. However, there can be any number of dividers in between.
public ObservableCollection<string> Bounds { get; set; }
...
//Initialize Bounds in the constructor
//Called when the selected item for DVList (an unrelated ListBox) is changed
private void DVSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedDV = DVList.SelectedItem as DVWrapper;
if (selectedDV != null)
{
//Setting min/max
Bounds[0] = selectedDV.MinValue;
Bounds[Bounds.Count - 1] = selectedDV.MaxValue;
MyListBox.ItemsSource = Bounds;
}
}
My XAML looks like this:
<Window.Resources>
<Style x:Key="BoundsStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
...
<TextBox/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Focusable" Value="False"/>
</Style>
</Window.Resources>
...
<ListBox Name="MyListBox"
ItemContainerStyle="{StaticResource BoundsStyle}"/>
So when SaveAndSubmit is called, bounds ends up being what I had originally set it to in DVSelectionChanged. In other words, the listbox is not updating based on what the user has input into the textboxes contained in listbox. How can I get the updated ListBoxItems? I think my problem is similar to this one, but it's not working for me at the moment.
When I step through in the debugger, I can get individual ListBoxItems. However, their Content is empty. I'm looking into that right now.
You need to bind content of the textbox.
<TextBox/> need to change to <TextBox Content="{Binding}"/>
But follow MVVM else it will be difficult to find these errors.

Categories

Resources