I have an UWP application where I want to trim the text of the TextBlock if it goes beyond the third line and show "show more" link (tappable) in the end of the 3rd line.
I know to restrict number of lines I can use MaxLines property but it simply ignores the rest of the lines as if they don't exist. But I want to let the user know that there is some more text and he can tap on the show more link to navigate to the full text.
How can I achieve it?
Read the good topic which describes all step to create an expandable textblock
Also, view the source code on github
Here is the XAML code:
<Grid x:Name="LayoutRoot" Tapped="LayoutRoot_OnTap">
<Grid.RowDefinitions>
<RowDefinition Height = "Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
x:Name= "CommentTextBlock"
HorizontalAlignment= "Left"
TextWrapping= "Wrap"
Height= "Auto"
Width= "280" />
< StackPanel Grid.Row= "1"
Orientation= "Horizontal"
HorizontalAlignment= "Right"
x:Name= "ExpandHint"
Visibility= "Collapsed"
Margin= "0,5,0,0" >
< TextBlock Text= "View More" />
< TextBlock Margin= "10,0,10,0"
Text= "+" />
</ StackPanel >
</ Grid >
Here is C# part
public sealed partial class ExpandableTextBlock : UserControl
{
public ExpandableTextBlock()
{
this.InitializeComponent();
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text", typeof(string), typeof(ExpandableTextBlock), new PropertyMetadata(default(string), OnTextChanged));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctl = (ExpandableTextBlock)d;
ctl.CommentTextBlock.SetValue(TextBlock.TextProperty, (string)e.NewValue);
ctl.CommentTextBlock.SetValue(TextBlock.HeightProperty, Double.NaN);
ctl.CommentTextBlock.Measure(new Size(ctl.CommentTextBlock.Width, double.MaxValue));
double desiredheight = ctl.CommentTextBlock.DesiredSize.Height;
ctl.CommentTextBlock.SetValue(TextBlock.HeightProperty, (double)63);
if (desiredheight > (double)ctl.CommentTextBlock.GetValue(TextBlock.HeightProperty))
{
ctl.ExpandHint.SetValue(StackPanel.VisibilityProperty, Visibility.Visible);
ctl.MaxHeight = desiredheight;
}
else
{
ctl.ExpandHint.SetValue(StackPanel.VisibilityProperty, Visibility.Collapsed);
}
//Setting length of comments
var boundsWidth = Window.Current.Bounds.Width;
ctl.CommentTextBlock.SetValue(TextBlock.WidthProperty, boundsWidth);
}
public static readonly DependencyProperty CollapsedHeightProperty = DependencyProperty.Register(
"CollapsedHeight", typeof(double), typeof(ExpandableTextBlock), new PropertyMetadata(default(double), OnCollapsedHeightChanged));
public double CollapsedHeight
{
get { return (double)GetValue(CollapsedHeightProperty); }
set { SetValue(CollapsedHeightProperty, value); }
}
private static void OnCollapsedHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctl = (ExpandableTextBlock)d;
ctl.CollapsedHeight = (double)e.NewValue;
}
public static readonly DependencyProperty TextStyleProperty = DependencyProperty.Register(
"TextStyle", typeof(Style), typeof(ExpandableTextBlock), new PropertyMetadata(default(Style), OnTextStyleChanged));
public Style TextStyle
{
get { return (Style)GetValue(TextStyleProperty); }
set { SetValue(TextStyleProperty, value); }
}
private static void OnTextStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctl = (ExpandableTextBlock)d;
ctl.CommentTextBlock.SetValue(StyleProperty, (Style)e.NewValue);
}
private void LayoutRoot_OnTap(object sender, TappedRoutedEventArgs tappedRoutedEventArgs)
{
if ((Visibility)this.ExpandHint.GetValue(StackPanel.VisibilityProperty) == Visibility.Visible)
{
//transition
this.CommentTextBlock.SetValue(TextBlock.HeightProperty, Double.NaN);
this.ExpandHint.SetValue(StackPanel.VisibilityProperty, Visibility.Collapsed);
}
}
}
Related
I created a custom user control, which displays a string (Var1) in different colors, according to the file ColorCode1:
<local:MyFormattedTextControl Text="{Binding SelectedItem.Var1, ElementName=myListView, UpdateSourceTrigger=PropertyChanged}" PartFlags="{Binding SelectedItem.ColorCode1, ElementName=myListView}" />
This works perfectly except for one detail: I cannot use TextWrapping="Wrap"
Can someone tell me how to update the User Control to be able to use TextWrappin?
The User Control looks like that:
<UserControl
<UserControl.Resources>
<local:PartColorValueConverter x:Key="partColorValueConv" />
</UserControl.Resources>
<Grid>
<StackPanel x:Name="myStackPanel" Orientation="Horizontal"/>
</Grid>
</UserControl>
Code Behind for this User Control:
public partial class MyFormattedTextControl : UserControl, INotifyPropertyChanged {
//Constructor
public MyFormattedTextControl() {
InitializeComponent();
myStackPanel.DataContext = this;
}
//Creating Dependency Properties for Text and ColorCode
public string Text {
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public IEnumerable<int> PartFlags {
get { return (int[])GetValue(PartFlagsProperty); }
set { SetValue(PartFlagsProperty, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(MyFormattedTextControl), new PropertyMetadata(null, OnTextPropertyChanged));
public static readonly DependencyProperty PartFlagsProperty = DependencyProperty.Register("PartFlags", typeof(int[]), typeof(MyFormattedTextControl), new PropertyMetadata(new int[] { }));
//OnPropertyChange
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
//Get Object
var ctrl = (MyFormattedTextControl)d;
var t = (string)e.NewValue;
ctrl.myStackPanel.Children.Clear();
//Fill Array with single characters
for (int i = 0; i < t.Length; i++) {
//Create TextBox
TextBlock tb = new TextBlock();
tb.Text = t.Substring(i, 1);
Binding b = new Binding("PartFlags");
PartColorValueConverter conv = new PartColorValueConverter();
b.Converter = conv;
b.ConverterParameter = i;
tb.SetBinding(ForegroundProperty, b);
//Add Text Box to StackPanel
ctrl.myStackPanel.Children.Add(tb);
}
}
private void OnPropertyChanged([CallerMemberName] string callerMember = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(callerMember));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Your problem is because you use many textblocks rather than one.
A Textblock contains Inlines. These can be various things, like newline. The most common is a run.
Hence you can do:
<TextBlock TextWrapping="Wrap">
<Run Foreground="Red">A</Run>
<Run Foreground="Blue">b</Run>
<Run Foreground="Green">c</Run>
</TextBlock>
Clear inlines and add a run per letter with the appropriate text and foreground.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.textblock.inlines?view=netframework-4.8
All:
The more I search for solutions to this question the more confused I become. After spending 12-16 hours watching YouTube, reading StackOverflow and general goggling, I thought I'd plead for additional help.
I would like to create a custom control so I can write various apps to remote to my video switcher.
I've created my control with dependency properties and that part is working well.
This answer on SO seemed to get me close, but I still can't get my app to run.
How to wire up a click event for a custom usercontrol button? Should I use CustomControl?
What I simply want to do is click btnIn1 in the control and have it return a "1", btnIn2 returns a "2" and so on.
I've also read about delegates, ICommand, TemplateParts and MVVM patterns which all seem like incredibly complex ways to click a button within a group. Maybe there's just not a simple way to do it.
Here's what I have so far. I simplified everything to a 2x2 matrix switcher (rather than the 4x4 I'm working on)
Thanks for all your help.
Norm
Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VideoSwitcher"
xmlns:enk="clr-namespace:VideoSwitcher.Controls">
<Style TargetType="{x:Type enk:Matrix44}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type enk:Matrix44}">
<Grid x:Name="grdBase" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40*"/>
<RowDefinition Height="100*"/>
<RowDefinition Height="40*"/>
<RowDefinition Height="100*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label x:Name="lblInputHeader" Content="Input"
Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="4"
Visibility="{TemplateBinding HeaderVisible}"
FontFamily="{TemplateBinding HeaderFont}"
FontSize="{TemplateBinding HeaderFontSize}"/>
<StackPanel Orientation="Horizontal"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="4">
<Button x:Name="btnIn1"
Margin="{TemplateBinding ButtonMargin}"
Height="{TemplateBinding ButtonHeight}"
Width="{TemplateBinding ButtonWidth}"
Content="{TemplateBinding Input1Label}"
Click="btnIn1Click"/>
<Button x:Name="btnIn2"
Margin="{TemplateBinding ButtonMargin}"
Height="{TemplateBinding ButtonHeight}"
Width="{TemplateBinding ButtonWidth}"
Content="{TemplateBinding Input2Label}"
Click="btnIn2Click"/>
</StackPanel>
<Label x:Name="lblOutputHeader" Content="Output"
Grid.Column="0"
Grid.Row="2"
Grid.ColumnSpan="4"
Visibility="{TemplateBinding HeaderVisible}"
FontFamily="{TemplateBinding HeaderFont}"
FontSize="{TemplateBinding HeaderFontSize}"/>
<StackPanel Orientation="Horizontal"
Grid.Column="0"
Grid.Row="3"
Grid.ColumnSpan="4">
<Button x:Name="btnOut1"
Margin="{TemplateBinding ButtonMargin}"
Height="{TemplateBinding ButtonHeight}"
Width="{TemplateBinding ButtonWidth}"
Content="{TemplateBinding Output1Label}"
Click="btnOut1Click"/>
<Button x:Name="btnOut2"
Margin="{TemplateBinding ButtonMargin}"
Height="{TemplateBinding ButtonHeight}"
Width="{TemplateBinding ButtonWidth}"
Content="{TemplateBinding Output2Label}"
Click="btnOut2Click"/>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Custom Control (Matrix44.cs)
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.ComponentModel;
namespace VideoSwitcher.Controls
{
public class Matrix44 : Control
{
#region Events - Go here if I can ever find out how to use them
#endregion
//This does not work --------
public event RoutedEventHandler Click;
void btnIn1Click(object sender, RoutedEventArgs e)
{
if (this.Click != null)
{
this.Click(this, e);
}
}
void btnIn2Click(object sender, RoutedEventArgs e)
{
if (this.Click != null)
{
this.Click(this, e);
}
}
void btnOut1Click(object sender, RoutedEventArgs e)
{
if (this.Click != null)
{
this.Click(this, e);
}
}
void btnOut2Click(object sender, RoutedEventArgs e)
{
if (this.Click != null)
{
this.Click(this, e);
}
}
#region Properties - Exposed to the user in the Properties panel, XAML or code-behind
#region Switcher Appearance Properies (Height, Width, Margin)
[Category("Switcher Appearance Properties")]
public double ButtonHeight
{
get { return (double)GetValue(ButtonHeightProperty); }
set { SetValue(ButtonHeightProperty, value); }
}
public static readonly DependencyProperty ButtonHeightProperty =
DependencyProperty.Register(nameof(ButtonHeight), typeof(double), typeof(Matrix44), new PropertyMetadata(50.0));
[Category("Switcher Appearance Properties")]
public double ButtonWidth
{
get { return (double)GetValue(ButtonWidthProperty); }
set { SetValue(ButtonWidthProperty, value); }
}
public static readonly DependencyProperty ButtonWidthProperty =
DependencyProperty.Register(nameof(ButtonWidth), typeof(double), typeof(Matrix44), new PropertyMetadata(50.0));
[Category("Switcher Appearance Properties")]
public Thickness ButtonMargin
{
get { return (Thickness)GetValue(ButtonMarginProperty); }
set { SetValue(ButtonMarginProperty, value); }
}
public static readonly DependencyProperty ButtonMarginProperty =
DependencyProperty.Register("ButtonMargin", typeof(Thickness), typeof(Matrix44));
#endregion
#region Labels
[Category("Switcher Label Properties")]
public string Input1Label
{
get { return (string)GetValue(Input1LabelProperty); }
set { SetValue(Input1LabelProperty, value); }
}
public static readonly DependencyProperty Input1LabelProperty =
DependencyProperty.Register(nameof(Input1Label), typeof(string), typeof(Matrix44), new PropertyMetadata("Input 1"));
[Category("Switcher Label Properties")]
public string Input2Label
{
get { return (string)GetValue(Input2LabelProperty); }
set { SetValue(Input2LabelProperty, value); }
}
public static readonly DependencyProperty Input2LabelProperty =
DependencyProperty.Register(nameof(Input2Label), typeof(string), typeof(Matrix44), new PropertyMetadata("Input 2"));
[Category("Switcher Label Properties")]
public string Output1Label
{
get { return (string)GetValue(Output1LabelProperty); }
set { SetValue(Output1LabelProperty, value); }
}
public static readonly DependencyProperty Output1LabelProperty =
DependencyProperty.Register(nameof(Output1Label), typeof(string), typeof(Matrix44), new PropertyMetadata("Output 1"));
[Category("Switcher Label Properties")]
public string Output2Label
{
get { return (string)GetValue(Output2LabelProperty); }
set { SetValue(Output2LabelProperty, value); }
}
public static readonly DependencyProperty Output2LabelProperty =
DependencyProperty.Register(nameof(Output2Label), typeof(string), typeof(Matrix44), new PropertyMetadata("Output 2"));
#endregion
#region Header Properties
[Category("Switcher Header Properties")]
public Visibility HeaderVisible
{
get { return (Visibility)GetValue(HeaderVisibleProperty); }
set { SetValue(HeaderVisibleProperty, value); }
}
public static readonly DependencyProperty HeaderVisibleProperty =
DependencyProperty.Register("HeaderVisible", typeof(Visibility), typeof(Matrix44));
[Category("Switcher Header Properties")]
public FontFamily HeaderFont
{
get { return (FontFamily)GetValue(HeaderFontProperty); }
set { SetValue(HeaderFontProperty, value); }
}
public static readonly DependencyProperty HeaderFontProperty =
DependencyProperty.Register("HeaderFont", typeof(FontFamily), typeof(Matrix44));
[Category("Switcher Header Properties")]
public double HeaderFontSize
{
get { return (double)GetValue(HeaderFontSizeProperty); }
set { SetValue(HeaderFontSizeProperty, value); }
}
public static readonly DependencyProperty HeaderFontSizeProperty =
DependencyProperty.Register("HeaderFontSize", typeof(double), typeof(Matrix44));
#endregion
#region Channel Properties
[Category("Switcher Channel Properties")]
//Channel Property - use to extend switcher tool capabilties; e.g. add new bank of ins/outs and remap input 1 to input 5 on 2nd bank
public int Input1Channel
{
get { return (int)GetValue(Input1ChannelProperty); }
set { SetValue(Input1ChannelProperty, value); }
}
public static readonly DependencyProperty Input1ChannelProperty =
DependencyProperty.Register("Input1Channel", typeof(int), typeof(Matrix44), new PropertyMetadata(1));
[Category("Switcher Channel Properties")]
public bool Input1Enabled
{
get { return (bool)GetValue(Input1EnabledProperty); }
set { SetValue(Input1EnabledProperty, value); }
}
public static readonly DependencyProperty Input1EnabledProperty =
DependencyProperty.Register("Input1Enabled", typeof(bool), typeof(Matrix44), new PropertyMetadata(false));
[Category("Switcher Channel Properties")]
public int Input2Channel
{
get { return (int)GetValue(Input2ChannelProperty); }
set { SetValue(Input2ChannelProperty, value); }
}
public static readonly DependencyProperty Input2ChannelProperty =
DependencyProperty.Register("Input2Channel", typeof(int), typeof(Matrix44), new PropertyMetadata(2));
[Category("Switcher Channel Properties")]
public bool Input2Enabled
{
get { return (bool)GetValue(Input1EnabledProperty); }
set { SetValue(Input1EnabledProperty, value); }
}
[Category("Switcher Channel Properties")]
public int Output1Channel
{
get { return (int)GetValue(Output1ChannelProperty); }
set { SetValue(Output1ChannelProperty, value); }
}
//Output channels
public static readonly DependencyProperty Output1ChannelProperty =
DependencyProperty.Register("Output1Channel", typeof(int), typeof(Matrix44), new PropertyMetadata(1));
[Category("Switcher Channel Properties")]
public int Output2Channel
{
get { return (int)GetValue(Output2ChannelProperty); }
set { SetValue(Output2ChannelProperty, value); }
}
public static readonly DependencyProperty Output2ChannelProperty =
DependencyProperty.Register("Output2Channel", typeof(int), typeof(Matrix44), new PropertyMetadata(2));
#endregion
#endregion
public Matrix44()
{
DefaultStyleKey = typeof(Matrix44);
}
}
}
MainWindow.XAML
<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:VideoSwitcher"
xmlns:Controls="clr-namespace:VideoSwitcher.Controls" x:Class="VideoSwitcher.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="200">
<Grid Margin="0,1,0,0">
<Controls:Matrix44 x:Name="swtMatrix"
HorizontalAlignment="Left"
Margin="10,10,0,0"
VerticalAlignment="Top"
ButtonHeight="65"
ButtonMargin="4"
ButtonWidth="65"/>
<Button x:Name="btnTake"
Content="Take"
HorizontalAlignment="Left"
Margin="10,213,0,0"
VerticalAlignment="Top"
Width="146"
Height="45" Click="btnTake_Click"/>
</Grid>
MainWindow.xaml.cs
using System.Windows;
namespace VideoSwitcher
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public int VideoInputChannel { get; set; }
public int VideoOutputChannel { get; set; }
public MainWindow()
{
InitializeComponent();
AddLabelsToMatrix();
}
public void AddLabelsToMatrix()
{
swtMatrix.Input1Label = "DVR1";
swtMatrix.Input2Label = "DVR2";
swtMatrix.Output1Label = "Videowall";
swtMatrix.Output2Label = "US Right";
}
private void btnTake_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Input channel: " + VideoInputChannel + " routed to Output: " + VideoOutputChannel);
}
/* switcher button psuedo-code
btnIn1_Click (object sender, RoutedEventArgs e)
{
//get input channel of matrix switcher
VideoInputChannel = swtMatrix.btnIn1.Channel;
}
btnIn2_Click (object sender, RoutedEventArgs e)
{
//get input channel of matrix switcher
VideoInputChannel = swtMatrix.btnIn2.Channel;
}
btnOut1_Click (object sender, RoutedEventArgs e)
{
//get output channel of matrix switcher
VideoInputChannel = swtMatrix.btnIn1.Channel;
}
btnOut2_Click (object sender, RoutedEventArgs e)
{
//get output channel of matrix switcher
VideoInputChannel = swtMatrix.btnIn2.Channel;
}
*/
}
}
You could override the OnApplyTemplate() method to get a reference to each Button and then hook up the event handlers:
public class Matrix44 : Control
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Button btnOut1 = this.Template.FindName("btnOut1", this) as Button;
if (btnOut1 != null)
btnOut1.Click += btnIn1Click;
//...and so on for each Button
}
}
You could then either raise a specific event for each Button or define a custom EventArgs that can be used to identify which Button that was clicked in an event handler:
C# event with custom arguments
I keep a list of current alarms and events in a basic datagrid, the user can scroll through the list. The user can select one item and acknowledge that specific alarm. This works.
The user should also be aloud to acknowledge all visible items. This is where im stuck. Is it possible to retrieve a list of currentcollectionshown from a datagrid?
Part of XAML
<Grid Background="DarkGray">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<Button Content="Ack visible" Command="{Binding AcknowledgeVisibleCommand}"/>
<Button Content="Ack selected" Command="{Binding AcknowledgeSelectedCommand}"/>
</StackPanel>
<DataGrid ItemsSource="{Binding AlarmAndEventList}" SelectedItem="{Binding SelectedAlarmItem}" Background="DarkGray" IsReadOnly="True">
</DataGrid>
</StackPanel>
</Grid>
Part of ViewModel
public ObservableCollection<AlarmItem> AlarmAndEventList { get { return _AlarmAndEventList; } }
private void AcknowledgeVisible()
{
//Do something with a list of visible items in datagrid
}
private void AcknowledgeSelected()
{
if (SelectedAlarmItem != null)
{
AlarmAndEventInstance.updateAlarmItem(SelectedAlarmItem);
}
}
Also, I want to run the single acknowledge command if the user double clicks an item. What is the "MVVM Way" of responding to a double click on a datagrid?
To observe the currently visible items in a DataGrid I have written the following attached-property:
public class DataGridExtensions
{
public static readonly DependencyProperty ObserveVisiblePersonsProperty = DependencyProperty.RegisterAttached(
"ObserveVisiblePersons", typeof(bool), typeof(DataGridExtensions),
new PropertyMetadata(false, OnObserveVisiblePersonsChanged));
public static readonly DependencyProperty VisiblePersonsProperty = DependencyProperty.RegisterAttached(
"VisiblePersons", typeof(List<Person>), typeof(DataGridExtensions),
new PropertyMetadata(null));
private static readonly DependencyProperty SenderDataGridProperty = DependencyProperty.RegisterAttached(
"SenderDataGrid", typeof(DataGrid), typeof(DataGridExtensions), new PropertyMetadata(default(DataGrid)));
private static void OnObserveVisiblePersonsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataGrid dataGrid = d as DataGrid;
if (dataGrid == null)
return;
dataGrid.Loaded += DataGridLoaded;
}
private static void DataGridLoaded(object sender, RoutedEventArgs routedEventArgs)
{
DataGrid dataGrid = (DataGrid) sender;
dataGrid.Loaded -= DataGridLoaded;
ScrollViewer scrollViewer = FindChildren<ScrollViewer>(dataGrid).FirstOrDefault();
if (scrollViewer != null)
{
SetSenderDataGrid(scrollViewer, dataGrid);
scrollViewer.ScrollChanged += ScrollViewerOnScrollChanged;
}
}
public static void SetObserveVisiblePersons(DependencyObject element, bool value)
{
element.SetValue(ObserveVisiblePersonsProperty, value);
}
public static bool GetObserveVisiblePersons(DependencyObject element)
{
return (bool) element.GetValue(ObserveVisiblePersonsProperty);
}
private static void SetSenderDataGrid(DependencyObject element, DataGrid value)
{
element.SetValue(SenderDataGridProperty, value);
}
private static DataGrid GetSenderDataGrid(DependencyObject element)
{
return (DataGrid) element.GetValue(SenderDataGridProperty);
}
private static void ScrollViewerOnScrollChanged(object sender, ScrollChangedEventArgs e)
{
ScrollViewer scrollViewer = sender as ScrollViewer;
if (scrollViewer == null)
return;
ScrollBar verticalScrollBar =
FindChildren<ScrollBar>(scrollViewer).FirstOrDefault(s => s.Orientation == Orientation.Vertical);
if (verticalScrollBar != null)
{
DataGrid dataGrid = GetSenderDataGrid(scrollViewer);
int totalCount = dataGrid.Items.Count;
int firstVisible = (int) verticalScrollBar.Value;
int lastVisible = (int) (firstVisible + totalCount - verticalScrollBar.Maximum);
List<Person> visiblePersons = new List<Person>();
for (int i = firstVisible; i <= lastVisible; i++)
{
visiblePersons.Add((Person) dataGrid.Items[i]);
}
SetVisiblePersons(dataGrid, visiblePersons);
}
}
public static void SetVisiblePersons(DependencyObject element, List<Person> value)
{
element.SetValue(VisiblePersonsProperty, value);
}
public static List<Person> GetVisiblePersons(DependencyObject element)
{
return (List<Person>) element.GetValue(VisiblePersonsProperty);
}
private static IList<T> FindChildren<T>(DependencyObject element) where T : FrameworkElement
{
List<T> retval = new List<T>();
for (int counter = 0; counter < VisualTreeHelper.GetChildrenCount(element); counter++)
{
FrameworkElement toadd = VisualTreeHelper.GetChild(element, counter) as FrameworkElement;
if (toadd != null)
{
T correctlyTyped = toadd as T;
if (correctlyTyped != null)
{
retval.Add(correctlyTyped);
}
else
{
retval.AddRange(FindChildren<T>(toadd));
}
}
}
return retval;
}
}
And in the xaml in the definition of your DataGrid you have to write the following:
nameSpaceOfAttachedProperty:DataGridExtensions.ObserveVisiblePersons="True"
nameSpaceOfAttachedProperty:DataGridExtensions.VisiblePersons="{Binding VisiblePersons, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
And in your ViewModel you will have a property like:
private List<Person> visiblePersons;
public List<Person> VisiblePersons
{
get { return visiblePersons; }
set
{
visiblePersons = value;
OnPropertyChanged();
}
}
And every time you scroll the VisiblePersons will be updated.
In your case you have to changed the type of the List inside the attached-propertay to match your requirements.
You can use MyDataGrid.Items collection to filter out.
You can also apply filtering in your MyDataGrid. But don't show filtered items. How to filter DataGrid
similar to my Labeled TextBox, which issues are resolved in:
Labeled TextBox in Windows Universal App
I got two issues in my Labeled Combobox, but first the Code:
Generic.xaml:
<Style TargetType="template:LabeledComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="template:LabeledComboBox">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="{TemplateBinding Label}" FontWeight="Bold" VerticalAlignment="Center" Margin="10,0" />
<ComboBox x:Name="PART_ComboBox" ItemsSource="{TemplateBinding ItemsSource}" SelectedIndex="{TemplateBinding SelectedIndex}" SelectedValue="{TemplateBinding SelectedValue}" SelectedValuePath="{TemplateBinding SelectedValuePath}" DisplayMemberPath="{TemplateBinding DisplayMemberPath}" VerticalAlignment="Center" Margin="20,0,10,0" Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
LabeledComboBox.cs:
[TemplatePart(Name = "PART_ComboBox", Type = typeof(ComboBox))]
public sealed class LabeledComboBox : Control, IParameterReturnable
{
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(LabeledComboBox), new PropertyMetadata(""));
public string Label
{
get { return GetValue(LabelProperty).ToString(); }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(object), typeof(LabeledComboBox), new PropertyMetadata(null));
public object ItemsSource
{
get { return GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty SelectedIndexProperty = DependencyProperty.Register("SelectedIndex", typeof(int), typeof(LabeledComboBox), new PropertyMetadata(default(int)));
public int SelectedIndex
{
get { return (int) GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register("SelectedValue", typeof(object), typeof(LabeledComboBox), new PropertyMetadata(null));
public object SelectedValue
{
get { return GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
public static readonly DependencyProperty SelectedValuePathProperty = DependencyProperty.Register("SelectedValuePath", typeof(string), typeof(LabeledComboBox), new PropertyMetadata(default(string)));
public string SelectedValuePath
{
get { return GetValue(SelectedValuePathProperty).ToString(); }
set { SetValue(SelectedValuePathProperty, value); }
}
public static readonly DependencyProperty DisplayMemberPathProperty = DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(LabeledComboBox), new PropertyMetadata(default(string)));
public string DisplayMemberPath
{
get { return GetValue(DisplayMemberPathProperty).ToString(); }
set { SetValue(DisplayMemberPathProperty, value); }
}
private ComboBox _comboBox;
public LabeledComboBox()
{
this.DefaultStyleKey = typeof(LabeledComboBox);
}
public LabeledComboBox(List<Parameter> parameterList)
{
this.Label = parameterList[0].DisplayName ?? "";
this.ItemsSource = parameterList;
this.SelectedValuePath = "DefaultValue";
this.DisplayMemberPath = "DefaultValue";
this.SelectedIndex = 0;
this.DefaultStyleKey = typeof(LabeledComboBox);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
_comboBox = GetTemplateChild("PART_ComboBox") as ComboBox;
if (_comboBox != null)
{
_comboBox.SelectionChanged += OnComboBoxSelectionChanged;
if (_comboBox.Items != null)
{
this.SelectedIndex = 0;
_comboBox.SelectedValue = _comboBox.Items[this.SelectedIndex];
}
}
}
private void OnComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedValue = _comboBox.SelectedValue;
}
public string GetKey()
{
return Label;
}
public string GetValue()
{
return SelectedValue.ToString();
}
}
It will be called in two different ways:
Dynamically in C#:
stackPanel.Add(new LabeledComboBox(parameterList));
Static in Xaml:
<templates:LabeledComboBox Label="Kategorien:" ItemsSource="{Binding ElementName=pageRoot, Path=FeedCategories}" DisplayMemberPath="Name" SelectedValuePath="Name" />
As I said before I got two issues with it:
How can I bind the SelectionChangedEvent to access it in Xaml || C#
As you can see, I try to preselect the first Item, which does not work and I don't know how to do it right
Thank you very much for all helpful and well meant answers in advance!
Instead of creating a custom control and recreating all needed dependency properties, I would suggest you use the Header and HeaderTemplate properties of the built in ComboBox, which will be displayed, just like in your LabeledComboBox, above the selection menu. Additionally the SelectionChanged event will be available.
So the usage in XAML would look like the following:
<ComboBox
DisplayMemberPath="Name"
Header="Kategorien:"
ItemsSource="{Binding ElementName=pageRoot, Path=FeedCategories}"
SelectedValuePath="Name"
SelectionChanged="OnSelectionChanged">
<ComboBox.HeaderTemplate>
<DataTemplate>
<TextBlock
Margin="10,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding}" />
</DataTemplate>
</ComboBox.HeaderTemplate>
</ComboBox>
But if you don't want to use the above method, to expose the selection changed event in your LabeledComboBox, add the following code:
private void OnComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedValue = _comboBox.SelectedValue;
this.RaiseSelectionChanged(e);
}
public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
private void RaiseSelectionChanged(SelectionChangedEventArgs args)
{
if (SelectionChanged != null)
{
SelectionChanged(this, args);
}
}
Then you can use the created SelectionChanged event from XAML.
we have a wpf-window with some textboxes and a datagrid.
the textboxes descripe a parent (class a) object and the datagrid lists a collection of "childs" (class b => not derived from class a).
the childs can inherit values from the parent.
for example if the parent (class a) has a property Foo then the child object (class b) has a property Nullable which can either override the value of the parent or inherit the value of the parent.
now the datagrid should display the value in gray (if it is inherited) or in black (if the user overrides the value in the grid cell).
Unfortunatly Binding to InheritedText doesnt work. Does someone have any idea?
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<UserControls:InheritedTextBoxControl
Text="{Binding Path=?}"
InheritedText="{Binding Path=?}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Thanks in advance
Tobi
--UPDATE--
xaml of InheritedTextBoxControl:
<UserControl x:Class="Com.QueoMedia.CO2Simulationstool.WPF.Utils.UserControls.InheritedTextBoxControl"
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"
mc:Ignorable="d"
Width="Auto"
Height="Auto"
Name="cnt">
<Grid x:Name="LayoutRoot"
Background="White">
<TextBox TextChanged="TextBoxTextChanged"></TextBox>
<TextBlock Name="inheritedText"
IsHitTestVisible="False"
Margin="4,0"
VerticalAlignment="Center"
Opacity="0.5"
FontStyle="Italic"></TextBlock>
</Grid>
CodeBehind:
public partial class InheritedTextBoxControl : UserControl {
private bool _isInherited;
public static readonly DependencyProperty InheritedTextProperty = DependencyProperty.Register("InheritedText", typeof(String), typeof(InheritedTextBoxControl), new PropertyMetadata(""));
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(InheritedTextBoxControl), new PropertyMetadata(default(string)));
public InheritedTextBoxControl() {
InitializeComponent();
}
public string InheritedText {
get { return (string)GetValue(InheritedTextProperty); }
set {
SetValue(InheritedTextProperty, value);
inheritedText.Text = value;
}
}
private bool IsInherited {
get { return _isInherited; }
set {
_isInherited = value;
if (value) {
inheritedText.Opacity = 0.5;
} else {
inheritedText.Opacity = 0;
}
}
}
public string Text {
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
private void TextBoxTextChanged(object sender, TextChangedEventArgs e) {
if (((TextBox)sender).Text.Length > 0) {
IsInherited = false;
} else {
IsInherited = true;
}
Text = ((TextBox)sender).Text;
}
}
The problem is the setter of your InheritedText property. WPF won't call this setter when the property is set from XAML. See Checklist for Defining a Dependency Property, section Implementing the "Wrapper" for details.
You will have to update inheritedText.Text in a PropertyChangedCallback like this:
public static readonly DependencyProperty InheritedTextProperty =
DependencyProperty.Register(
"InheritedText", typeof(string), typeof(InheritedTextBoxControl),
new PropertyMetadata(string.Empty, InheritedTextChanged));
private static void InheritedTextChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
((InheritedTextBoxControl)d).inheritedText.Text = (string)e.NewValue;
}
public string InheritedText
{
get { return (string)GetValue(InheritedTextProperty); }
set { SetValue(InheritedTextProperty, value); } // only call SetValue here
}
If someone is interested in the solution:
we did it using a CellTemplate containing a CustomControl name MaskedTextbox that has three properties (MaskedText, Text, IsMaskTextVisible) and a CellEditingTemplate to override the data.
The values are bound to an InheritableValueViewModel.
Tobi