Usercontrol in datatemplate out of sync - c#

So I want to have multiple tabs with a usercontrol in it, depending on how many items there are in a list. so I think this shoudn t be too hard but .... I start with a new window to make a tabcontroller and bind its itemsource(of the tabcontroler to the list):
<Window x:Class="xxxxx.extraforms.frmownedchamps"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sp="clr-xxxxx.usercontrols.ucoptions"
Title="frmownedchamps" Height="593" Width="350" Loaded="Window_Loaded_1" ResizeMode="NoResize" WindowStyle="None" ShowInTaskbar="False">
<Grid>
<TabControl Name="thetabcontrol">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding name}" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type sp:ucownedchampviewer}" >
<sp:ucownedchampviewer strname="{Binding Path=name}" strcode="{Binding Path=code}" clclist="{Binding Path=list}" teller="{Binding Path=teller}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
once the window get loaded it only does thetabcontrol.ItemsSource = settings.clclist;
the clclist is this:
public static List<clc> clclist { get; set; }
public void methodblabla()
{
foreach( xml blabla)
{
clc clctemp = new clc(xmlname, xmlcode);
clclist.Add(clctemp);
}
}
the clc class is:
public class clc
{
private static int counter = 0;
public int teller { get; set; }
public String name { get; set; }
public String code { get; set; }
public ObservableCollection<champion> list { get; set; }
public clc(String name, String code)
{
this.name = name;
this.code = code;
teller = counter;
counter++;
makelist();
}
public void makelist()
{
var bytes = Convert.FromBase64String(code);
var values = new System.Collections.BitArray(bytes);
list = new ObservableCollection<champion>();
int aantal = champions.list.Count;
int teller = 0;
int counter = 0;
for (int x = aantal; x != 0; x--)
{
if (values[x - 1] == true)
{
list.Add(champions.getchampbyid(counter + 1));
teller++;
}
counter++;
}
}
}
my usercontrol:
<UserControl x:Class="xxxxx.usercontrols.ucoptions.ucownedchampviewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sp="clr-namespace:xxxxx"
mc:Ignorable="d"
d:DesignHeight="564" d:DesignWidth="350" Loaded="UserControl_Loaded">
<Grid Height="624">
<Grid.Resources>
<Style x:Key="Ownedstyle" TargetType="{x:Type ListViewItem}">
<Setter Property="Background" Value="Red"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding strowned}" Value="Yes">
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<StackPanel Margin="0,0,0,12">
<StackPanel Orientation="Horizontal" Margin="5">
<TextBox Name="txtclc" Width="250" Margin="2" Text="{Binding Path=strcode ,RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
<Button Name="btnload" Content="Save" Click="btnsave_Click" Width="55" Margin="2"/>
</StackPanel>
<Line Margin="2" />
<StackPanel Orientation="Horizontal" Margin="5" HorizontalAlignment="Stretch" Width="350">
<TextBlock VerticalAlignment="Center">Filter:</TextBlock>
<TextBox Name="txtfilter" Height="30" Grid.Column="0" TextChanged="txtfilter_TextChanged" Margin="5" Width="250" />
<Label Name="lblaantal"></Label>
</StackPanel>
<ListView Name="lsvallchamps" Grid.Column="0" Grid.Row="1" Grid.RowSpan="1" Height="410" Width="auto" ItemContainerStyle="{StaticResource Ownedstyle}" ItemsSource="{Binding Path=clclist ,RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" >
<ListView.View>
<GridView>
<GridViewColumn Width="60">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image HorizontalAlignment="Center" VerticalAlignment="Center" Width="50" Height="50" Source="{Binding image}" Stretch="Fill"></Image>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="120" DisplayMemberBinding="{Binding name}">
<GridViewColumnHeader Content="name" Tag="name" Click="SortClick"/>
</GridViewColumn>
<GridViewColumn Width="130" DisplayMemberBinding="{Binding strroles}">
<GridViewColumnHeader Content="strroles" Tag="strroles" Click="SortClick" />
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<Button Content="testknop" Click="Button_Click" />
<Button Content="Hide" Name="btnhide" Width="150" Height="35" Margin="5" Click="btnhide_Click"></Button>
</StackPanel>
</Grid>
</UserControl>
Sorry for so much code, but mayby better too much then too less code. The problem is that I want the button btnsave to save the txtclc.text to the code and make a new list of it, (and then the listview should be automaticly changed according to it since it is binded to it)
However once I use the code
private void btnsave_Click(object sender, RoutedEventArgs e)
{
settings.clclist[teller].code = txtclc.Text;
settings.clclist[teller].makelist();
}
It does change it! I can see it with debug.writeline that the value has changed in the clclist. BUT the listview in this tab doesn t change! ONLY if I go to an other tab, then back to the first one then it has changed with the right champions. There even is a 2nd problem. O But if I go to an other tab(usercontrol) the txtclc.text is changed as well to the first one! also the list is not updated on the usercontrol, on neither! however the makelist method should change it?
Sorry for this LONG question however I ve been googeling alot and no luck for this problem.
SOLUTION:
Replace Text="{Binding Path=strcode ,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" with Text="{Binding Path=code}" in the usercontrol. and add INotifyPropertyChanged to the clc class, Thx to Rachel!

You have two problems here.
The first, is that your classes don't implement INotifyPropertyChanged, so the UI has no idea that it's objects have changed. Make your clc class implement this interface, and raise the PropertyChanged event when the code and name properties change so the UI knows to update.
The second problem is that by default, WPF will re-use a template if possible. For example, your TabControl is creating one instance of your UserControl, and when you switch tabs it is simply changing the DataContext behind the UserControl. If txtclc.Text is not bound to something in the DataContext, then it will not change because you are viewing the exact same TextBox regardless of which tab you are viewing. The solution is to add a property to your DataContext object to store the Text of the txtclc TextBox, and bind the property.

Related

Binding two XAML element properties to the same ViewModel property does not work

Problem description
I use the Master Details View control. It has two properties Details Header and SelectedItem. I bind both to the Selected property in ViewModel. The goal is to change the DetailsHeader header depending on the selected item. The problem is that only SelectedItem is updated. The text does not appear in the DetailsHeader.
DetailsHeader="{x:Bind ViewModel.Selected, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.Selected, Mode=OneWay}"
If link one to the other, it works correctly - both are updated.
DetailsHeader="{x:Bind masterDetailsView.SelectedItem, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.Selected, Mode=OneWay}"
I do not find in the documentation a restriction on the fact that a property from a context can only be bound to one property of a XAML element. What could be the problem?
Code
ViewModelBase from MVVM Light is used for notification of changes. Its Set() method updates the property and raises a change event.
private SampleVendorModel _selected;
public SampleVendorModel Selected
{
get => _selected;
set => Set(ref _selected, value);
}
Model
public class SampleVendorModel
{
public string Name { get; set; }
public string Surname { get; set; }
public string MiddleName { get; set; }
public string FullName => $"{Surname} {Name} {MiddleName}";
public string NameWithoutSurname => $"{Name} {MiddleName}";
}
MasterDetailsView
<controls:MasterDetailsView Name="masterDetailsView"
MasterHeader="{x:Bind ViewModel.Title}"
MasterHeaderTemplate="{StaticResource MasterHeaderTemplate}"
DetailsHeader="{x:Bind masterDetailsView.SelectedItem, Mode=OneWay}"
DetailsHeaderTemplate="{StaticResource DetailsHeaderTemplate}"
ItemsSource="{x:Bind ViewModel.Items, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.Selected, Mode=OneWay}"
ItemTemplate="{StaticResource ItemTemplate}"
DetailsTemplate="{StaticResource DetailsTemplate}"
BorderBrush="Transparent"
BackButtonBehavior="Manual">
</controls:MasterDetailsView>
Resource's page
<Page.Resources>
<Style x:Key="HeaderStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="30" />
<Setter Property="FontWeight" Value="Light" />
<Setter Property="Margin" Value="0, 10, 0, 10" />
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
</Style>
<DataTemplate x:Key="MasterHeaderTemplate">
<TextBlock Text="{Binding}" Style="{StaticResource HeaderStyle}"/>
</DataTemplate>
<DataTemplate x:Key="DetailsHeaderTemplate" x:DataType="viewmodels:SampleVendorModel">
<TextBlock Text="{x:Bind FullName}" Style="{StaticResource HeaderStyle}" />
</DataTemplate>
<DataTemplate x:Key="ItemTemplate" x:DataType="viewmodels:SampleVendorModel">
<Grid Margin="0, 10, 0, 10" RowSpacing="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{x:Bind Surname}" FontWeight="Bold" FontSize="16" TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Row="1" Text="{x:Bind NameWithoutSurname}" TextTrimming="CharacterEllipsis" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="DetailsTemplate"
x:DataType="viewmodels:SampleVendorModel">
<StackPanel Margin="10">
<TextBlock Text="{x:Bind Surname}" />
<TextBlock Text="{x:Bind Name}" />
<TextBlock Text="{x:Bind MiddleName}" />
</StackPanel>
</DataTemplate>
</Page.Resources>
The problem is that only SelectedItem is updated
I wronged. MasterDetailsView.SelectedItem is updated via property ViewModel.Selected in the code. In case UI navigation, ViewModel.Selected doesn't change. Because binding mode is OneWay. As a consequence, MasterDetailsView.DetailsHeader doesn't get notify about change selected.
In order to solve the problem, need to set mode as TwoWay for MasterDetailsView.SelectedItem.
DetailsHeader="{x:Bind ViewModel.Selected, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.Selected, Mode=TwoWay}"

Data bind an ItemsControl in a DataTemplate

I seem to have a simple data-binding problem, but can't figure out the right way to do it. There is a TabControl which defines two DataTemplate's, one for the tab header and one for the tab content.
The content template contains an ItemsControl. The ItemsControl tries to bind to a dynamically created ViewModel (ConnectionInfoVM).
When I display the UI, the binding just fails, but there is no error-message in the output about it.
How do I have to set up the DataContext and the binding so the binding works and the DataBuffer is actually displayed? Any help greatly appreciated.
ConnectionsControl:
<UserControl x:Class="XXXViewer.Views.ConnectionsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewModels="clr-namespace:XXXViewer.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TabControl Grid.Row="0" Name="TabDynamic" SelectionChanged="tabDynamic_SelectionChanged">
<TabControl.Resources>
<DataTemplate x:Key="TabHeader" DataType="TabItem">
<DockPanel>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
<Button Name="btnDelete" DockPanel.Dock="Right" Margin="5,0,0,0" Padding="0" Click="btnTabDelete_Click" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
<Image Source="{DynamicResource DeleteImg}" Height="11" Width="11"></Image>
</Button>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="TabContent" DataType="viewModels:ConnectionInfoVM">
<StackPanel>
<ScrollViewer Name="Scroller" Background="Black">
<StackPanel>
<TextBlock Text="This line gets printed" Foreground="White" FontFamily="Consolas"/>
<ItemsControl Name="ItemCtrl" ItemsSource="{Binding DataBuffer}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}" Foreground="White" FontFamily="Consolas"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</StackPanel>
</DataTemplate>
</TabControl.Resources>
</TabControl>
</Grid>
</UserControl>
ConnectionsControl code behind:
namespace XXXViewer.Views
{
public partial class ConnectionsControl : UserControl
{
private readonly ObservableCollection<TabItem> _tabItems = new ObservableCollection<TabItem>();
public ConnectionsControl()
{
InitializeComponent();
// bindings
TabDynamic.ItemsSource = _tabItems;
TabDynamic.DataContext = this;
}
// assume this gets called
private void AddTabItem(ConnectionInfoVM ci)
{
DataTemplate headerTemplate = TabDynamic.FindResource("TabHeader") as DataTemplate;
DataTemplate contentTemplate = TabDynamic.FindResource("TabContent") as DataTemplate;
// create new tab item
TabItem tab = new TabItem
{
Header = $"Tab {ci.ConnectionID}",
Name = $"T{ci.ConnectionID}",
HeaderTemplate = headerTemplate,
ContentTemplate = contentTemplate,
DataContext = ci
};
_tabItems.Insert(0, tab);
// set the new tab as active tab
TabDynamic.SelectedItem = tab;
}
}
}
ConnectionInfoVM:
namespace XXXViewer.ViewModels
{
public class ConnectionInfoVM : ViewModelBase
{
private readonly ObservableQueue<string> _dataBuffer = new ObservableQueue<string>();
public ObservableQueue<string> DataBuffer => _dataBuffer;
}
}
Screenshot of the tab that gets created:
resulting tab
You set the ContentTemplate but never the Content, so the ContentTemplate is never applied because it's applied only when there's Content set. Instead of DataContext = ci write Content = ci.
By the way the DataContext = ci was useless because the DataContext is already implicitely the object on which the DataTemplate is applied.
Edit
As you're using WPF, use and abuse of its core feature: bindings.
How I would have written your code (if I didn't use full MVVM compliant code):
Your XAML:
<TabControl Grid.Row="0" Name="TabDynamic"
ItemsSource="{Binding TabItems, Mode=OneWay}"
SelectionChanged="tabDynamic_SelectionChanged">
<TabControl.Resources>
<DataTemplate x:Key="TabHeader" DataType="TabItem">
<DockPanel>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
<Button Name="btnDelete" DockPanel.Dock="Right" Margin="5,0,0,0" Padding="0" Click="btnTabDelete_Click" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
<Image Source="{DynamicResource DeleteImg}" Height="11" Width="11"></Image>
</Button>
</DockPanel>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate DataType="viewModels:ConnectionInfoVM">
<TabItem Header="{Binding ConnectionID, Mode=OneWay}"
Name="{Binding ConnectionID, Mode=OneWay}"
HeaderTemplate="{StaticResources TabHeader}">
<StackPanel>
<ScrollViewer Name="Scroller" Background="Black">
<StackPanel>
<TextBlock Text="This line gets printed" Foreground="White" FontFamily="Consolas"/>
<ItemsControl Name="ItemCtrl" ItemsSource="{Binding DataBuffer}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}" Foreground="White" FontFamily="Consolas"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</StackPanel>
</TabItem>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
You cs code become much simpler:
namespace XXXViewer.Views
{
public partial class ConnectionsControl : UserControl
{
private readonly ObservableCollection<ConnectionInfoVM> _tabItems = new ObservableCollection<ConnectionInfoVM>();
public ObservableCollection<ConnectionInfoVM> TabItems {get {return _tabItems;}}
public ConnectionsControl()
{
InitializeComponent();
// bindings
//TabDynamic.ItemsSource = _tabItems;
TabDynamic.DataContext = this;
}
// assume this gets called
private void AddTabItem(ConnectionInfoVM ci)
{
TabItems.Add(ci);
}
}
}
I noted while re-reading your code that you were probably confused about binding in code-behind.
Your code TabDynamic.ItemsSource = _tabItems; is not a binding, it will only set it once.
Anyway, I suggest you read a bit about MVVM. The TabItems should be in a ViewModel class instead of being in code-behind.
The Tabcontrol as per your coding does not contain in it's DataContext the viewmodel but the control; so we need to find a control or something else which holds the VM. It does not appear that the page holds the VM in its DataContext either.
I recommend that one route is to use the TabControl's Tag property to hold the VM such as specifying it in code behind as such:
TabDynamic.ItemsSource = _tabItems;
TabDynamic.DataContext = this;
TabDynamic.Tag = {Wherever you are keeping your VM at this time its not clear in your code example};
Then you can specify Tag from the template binding by specifying the TabControls' name as such:
<ItemsControl Name="ItemCtrl"
ItemsSource="{Binding Tag.DataBuffer,
ElementName=TabDynamic}">

How to show properties of listbox selected item in different controls

In my application I have a window which contain a ListBox, and controls that should show different properties of its currently selected item. Those controls are:
TextBox that should show 'Name' property.
TextBox that should show 'DataFile` property.
DataGrid that should show 'TItems property, which is an ObservableCollection.
I tried to bind SelectedItem to an object, and then bind different properties of that object to the controls mentioned above, with no success.
The window:
My View:
<Window
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:ReportMaker"
xmlns:ViewModel="clr-namespace:ReportMaker.ViewModel" x:Class="ReportMaker.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<ViewModel:MainViewModel/>
</Window.DataContext>
<Grid>
<Button x:Name="button" Content="Create" HorizontalAlignment="Right" Margin="0,0,10,10" VerticalAlignment="Bottom" Width="75"/>
<ComboBox x:Name="comboBox" HorizontalAlignment="Left" Margin="10,0,0,10" VerticalAlignment="Bottom" Width="120"/>
<ListBox x:Name="listBox" HorizontalAlignment="Left" Margin="10,10,0,36.667" Width="119" ItemsSource="{Binding ReportItems}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel HorizontalAlignment="Left" Height="274" Margin="134,10,0,0" VerticalAlignment="Top" Width="375" DataContext="{Binding SelectedReportItem}">
<StackPanel.Resources>
<Style x:Key="ControlBaseStyle" TargetType="{x:Type Control}">
<Setter Property="Margin" Value="0, 10, 0, 0" />
</Style>
</StackPanel.Resources>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:"/>
<TextBox Width="150" Text="{Binding Name}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Data File:"/>
<TextBox Width="150" Text="{Binding ID}"/>
</StackPanel>
<DataGrid Height="190" VerticalAlignment="Bottom" ItemsSource="{Binding TItems}"/>
</StackPanel>
<Button x:Name="button_Copy" Content="Save" HorizontalAlignment="Right" Margin="0,0,92,10" VerticalAlignment="Bottom" Width="75"/>
</Grid>
</Window>
My ViewModel:
public class MainViewModel
{
public ObservableCollection<ReportItem> ReportItems { get; set; }
public object SelectedReportItem { get; set; }
public MainViewModel()
{
ReportItems = new ObservableCollection<ReportItem>();
ReportItems.Add(Example);
}
public ReportItem Example = new TextReportItem() { Name = "John", DataFile = "try.txt"};
}
ReportItem:
public class ReportItem
{
public int Id { get; set; }
public string Name { get; set; }
public string DataFile { get; set; }
}
TextReportItem:
public class TextReportItem : ReportItem
{
public ObservableCollection<TextParcel> TItems { get; set; }
}
public class TextParcel
{
char Delimiter { get; set; }
string LineExp { get; set; }
string Result { get; set; }
string IgnoreLine { get; set; }
int DesiredResultIndexInLine { get; set; }
}
EDIT: as I use MVVM, I prefer to use only XAML in the View, with no code behind.
EDIT 2:
Thanks to S.Akbari I succeeded to view the desired properties in the TextBox controls, with the following code:
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:"/>
<TextBox Width="150" Text="{Binding ElementName=listBox, Path=SelectedItem.Name}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Data File:"/>
<TextBox Width="150" Text="{Binding ElementName=listBox, Path=SelectedItem.DataFile}"/>
</StackPanel>
But when the same logic is applied to my DataGrid, it fails for some reason:
<DataGrid Height="190" VerticalAlignment="Bottom" ItemsSource="{Binding ElementName=listBox, Path=SelectedItem.TItmes}" />
I also tried:
<DataGrid Height="190" VerticalAlignment="Bottom" DataContext="{Binding ElementName=listBox, Path=SelectedItem}" ItemsSource="{Binding TItems}"/>
And also:
<DataGrid Height="190" VerticalAlignment="Bottom" DataContext="{Binding ElementName=listBox, Path=SelectedItem}">
<DataTemplate>
<TextBlock Text="{Binding TItems}" />
</DataTemplate>
</DataGrid>
if you use MVVM your view model should raise property changed events
You should implement INotifyPropertyChanged
and change the selected item to be a full property
see :How to: Implement Property Change Notification

C# wpf ListView seperate scrollbar for each columns in GridView

I want seperate ScrollBar for each columns
<ListView ItemsSource="{Binding}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
DisplayMemberBinding="{Binding name}"/>
<GridViewColumn Header="Price"
DisplayMemberBinding="{Binding price}"/>
</GridView>
</ListView.View>
</ListView>
and i want to have 2 horizontal scrollbar for each column (Name and Price),
my destination is to set up column with picture, but i want to scroll that picture when its to big.
I found a way to add scrolls only in the bottom of a gridview based on this solution: How can I create a group footer in a WPF ListView ( GridView ). It looks kinda hacky but it seems there is no already made solution.
What I did in xaml is bind Group description to a fake property for show the footer only one time.
XAML:
<PropertyGroupDescription PropertyName="fake" />
Then I modified a footer template to make it display a scrollbar:
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<DockPanel>
<Grid DockPanel.Dock="Bottom" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ElementName=first, Path=Width, UpdateSourceTrigger=PropertyChanged}" />
<ColumnDefinition Width="{Binding ElementName=second, Path=Width, UpdateSourceTrigger=PropertyChanged}" />
</Grid.ColumnDefinitions>
<ScrollBar Grid.Column="0" Orientation="Horizontal" Scroll="ScrollBar1_Scroll"
Width="{Binding ElementName=first, Path=Width, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" >
</ScrollBar>
<ScrollBar Grid.Column="1" Orientation="Horizontal" Scroll="ScrollBar2_Scroll"
Width="{Binding ElementName=second, Path=Width, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
</Grid>
<ItemsPresenter />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
There's a lot of bindings which are necessary for resizing scrollbars with columns. Another hack there: you have to initially set the Width for each GridViewColumn or scrollbars will appear only after you manually resize a column.
I move contents of cells changing Margin property of StackPanel in the CellTemplate.
Below is the whole code, it's not a full working solution, I think, but a good point to start.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Grid>
<Grid.Resources>
<CollectionViewSource x:Key="ViewSource" Source="{Binding Path=Collection, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window} }">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="fake" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Grid.Resources>
<HeaderedItemsControl Header="ListView">
<StackPanel>
<ListView ItemsSource="{Binding Source={StaticResource ViewSource}, UpdateSourceTrigger=PropertyChanged}" x:Name="Lv" Margin="5,5,5,5">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<DockPanel>
<Grid DockPanel.Dock="Bottom" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ElementName=first, Path=Width, UpdateSourceTrigger=PropertyChanged}" />
<ColumnDefinition Width="{Binding ElementName=second, Path=Width, UpdateSourceTrigger=PropertyChanged}" />
</Grid.ColumnDefinitions>
<ScrollBar Grid.Column="0" Orientation="Horizontal" Scroll="ScrollBar1_Scroll"
Width="{Binding ElementName=first, Path=Width, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" >
</ScrollBar>
<ScrollBar Grid.Column="1" Orientation="Horizontal" Scroll="ScrollBar2_Scroll"
Width="{Binding ElementName=second, Path=Width, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
</Grid>
<ItemsPresenter />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
<ListView.View >
<GridView >
<GridViewColumn Header="Name" x:Name="first" Width="50">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="{Binding Path=Offset1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window} }">
<TextBlock x:Name="tb1" Text="{Binding name}"></TextBlock>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Price" x:Name="second" Width="50">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="{Binding Path=Offset2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window} }" >
<TextBlock Text="{Binding price}"></TextBlock>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</HeaderedItemsControl>
</Grid>
</Window>
And code-behind
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls.Primitives;
namespace WpfApplication1
{
public partial class MainWindow:INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
}
private List<MyItem> _collection = new List<MyItem>();
public List<MyItem> Collection
{
get { return _collection; }
set { _collection = value; InvokePropertyChanged(new PropertyChangedEventArgs("Collection")); }
}
private Thickness _offset1;
public Thickness Offset1
{
get { return _offset1; }
set { _offset1 = value; InvokePropertyChanged(new PropertyChangedEventArgs("Offset1")); }
}
private Thickness _offset2;
public Thickness Offset2
{
get { return _offset2; }
set { _offset2 = value; InvokePropertyChanged(new PropertyChangedEventArgs("Offset2")); }
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Collection.Add(new MyItem("item1", "10"));
Collection.Add(new MyItem("item2", "200"));
Collection.Add(new MyItem("item3", "600000"));
Collection.Add(new MyItem("item4", "1"));
}
private void ScrollBar1_Scroll(object sender, ScrollEventArgs e)
{
//imitate scrolling here
double leftToRight = Offset1.Left;
if (e.ScrollEventType == ScrollEventType.SmallIncrement)
leftToRight += 5;
if (e.ScrollEventType == ScrollEventType.SmallDecrement)
leftToRight -= 5;
Offset1 = new Thickness(leftToRight, 0, 0, 0);
}
private void ScrollBar2_Scroll(object sender, ScrollEventArgs e)
{
//imitate scrolling here
double leftToRight = Offset2.Left;
if (e.ScrollEventType == ScrollEventType.SmallIncrement)
leftToRight += 5;
if (e.ScrollEventType == ScrollEventType.SmallDecrement)
leftToRight -= 5;
Offset2 = new Thickness(leftToRight, 0, 0, 0);
}
public event PropertyChangedEventHandler PropertyChanged;
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
}
public class MyItem
{
public string name { get; set; }
public string price { get; set; }
public MyItem(string n, string p)
{
name = n;
price = p;
}
}
}

How to make TextBlock Text Selectable on a WPF DataGrid Group Header or Anyother way to do it

I am working on displaying rows which are grouped on WPF DataGrid
Group Header which I am currently displaying is a TextBlock, and the text displayed on the TextBlock is not selectable.
How can I make TextBlock text selectable so that I can copy the value.
Following is the XAML.
I am using following code to Bind data to the grid and group the data.
Entity:
public class AverageCounter
{
public string CounterName { get; set; }
public string Role { get; set; }
public string RoleInstance { get; set; }
public decimal CounterAverageValue { get; set; }
}
Code to Bind and Group.
var results = new ListCollectionView(queryResultSet);
if (results.GroupDescriptions != null)
{
results.GroupDescriptions.Add(new PropertyGroupDescription("CounterName"));
}
dataGrid1.AutoGenerateColumns = true;
dataGrid1.ItemsSource = results;
XAML:
<Window x:Class="CheckPerfromanceCounters.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CheckPerfromanceCounters"
Title="MainWindow" Height="390" Width="878">
<Window.Resources>
<local:AvgConverter x:Key="avgConverter"/>
</Window.Resources>
<Grid>
<Button Content="Refresh" Height="23" HorizontalAlignment="Left" Margin="769,328,0,0" Name="button1"
VerticalAlignment="Top" Width="75" Click="Button1Click" />
<DataGrid AutoGenerateColumns="True" Height="310" HorizontalAlignment="Left" Margin="12,12,0,0"
Name="dataGrid1" VerticalAlignment="Top" Width="832">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=CounterName}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" FontStyle="Italic"/>
<TextBlock><Bold> - Average: </Bold></TextBlock>
<TextBlock Text="{Binding Converter={StaticResource avgConverter}}" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
</Grid>
</Window>
if you need anyother information please let me know.
Just use TextBox and make it readonly, then you can change the TextBox.Style to make it looks like TextBlock.
Perhaps something like this
<TextBox IsReadOnly="True"
BorderThickness="0"
Background="Transparent"
TextWrapping="Wrap" />
Use TextBox with IsReadOnly ="True" but also set the binding mode to oneWay :
Text="{Binding Converter={StaticResource avgConverter}, Mode=OneWay}"

Categories

Resources