DataGrid custom CanUserAddRows implementation - wpf

I want to implement a custom add row button to a DataGrid (it's a long story). I added the button in template, and define an attached property, and I can get the button's click. But I can not add a new row in a generic way -not for a specified type-. I know that I can do similar thing in ViewModel, but I'm looking to do this in templates and attached properties. Here is my try; Any idea to complete this?
XAML:
<Style x:Key="{x:Type DataGrid}" TargetType="{x:Type DataGrid}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGrid}">
<Border x:Name="border">
<ScrollViewer x:Name="DG_ScrollViewer" Focusable="false">
<ScrollViewer.Template>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button Focusable="false"
Command="{x:Static DataGrid.SelectAllCommand}" />
<DataGridColumnHeadersPresenter
x:Name="PART_ColumnHeadersPresenter"
Grid.Column="1" />
<Grid Grid.ColumnSpan="2" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="22" />
</Grid.RowDefinitions>
<ScrollContentPresenter
x:Name="PART_ScrollContentPresenter" />
<!-- THIS IS MY CUSTOM BUTTON TO ADD NEW ROW -->
<Button x:Name="PART_AddRowButton"
Content="Add"/>
</Grid>
<ScrollBar x:Name="PART_VerticalScrollBar"/>
<Grid Grid.Column="1" Grid.Row="2">
<ScrollBar x:Name="PART_HorizontalScrollBar"/>
</Grid>
</Grid>
</ControlTemplate>
</ScrollViewer.Template>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
C#:
public class DataGridHelper {
public static readonly DependencyProperty CanUserAddRowsProperty
= DependencyProperty.RegisterAttached(
"CanUserAddRows", typeof(bool), typeof(DataGridHelper),
new FrameworkPropertyMetadata(default(bool), CanUserAddRowsChanged));
[AttachedPropertyBrowsableForType(typeof(DataGrid))]
public static bool GetCanUserAddRows(DependencyObject obj) {
return (bool)obj.GetValue(CanUserAddRowsProperty);
}
[AttachedPropertyBrowsableForType(typeof(DataGrid))]
public static void SetCanUserAddRows(DependencyObject obj, bool value) {
obj.SetValue(CanUserAddRowsProperty, value);
}
private static void CanUserAddRowsChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e) {
var dataGrid = d as DataGrid;
if (dataGrid == null)
return;
bool oldValue = (bool)e.OldValue,
newValue = (bool)e.NewValue;
if (newValue == oldValue)
return;
if (newValue) {
dataGrid.Loaded += CanUserAddRowsDataGridLoaded;
} else {
dataGrid.Loaded -= CanUserAddRowsDataGridLoaded;
}
}
private static void CanUserAddRowsDataGridLoaded(object sender, RoutedEventArgs e) {
var dataGrid = sender as DataGrid;
if (dataGrid == null)
return;
if (dataGrid.Style == null)
return;
var rootTemplate = dataGrid.Template;
if (rootTemplate == null)
return;
var scroll = rootTemplate.FindName("DG_ScrollViewer", dataGrid) as ScrollViewer;
if (scroll == null)
return;
var scrollTemplate = scroll.Template;
if (scrollTemplate == null)
return;
var button = scrollTemplate.FindName("PART_AddRowButton", scroll) as ButtonBase;
if (button == null)
return;
if (GetCanUserAddRows(dataGrid)) {
button.Click += AddRowClicked;
} else {
button.Click -= AddRowClicked;
}
}
private static void AddRowClicked(object sender, RoutedEventArgs e) {
var button = ((ButtonBase)sender);
var parent = VisualTreeHelper.GetParent(button);
while (!(parent is DataGrid))
parent = VisualTreeHelper.GetParent(parent);
var source = ((DataGrid)parent).Items.Add(...) // now what???
}
}
Well, as you can see, I got access to DataGrid after button got clicked; But what is next? How can I force the DataGrid to show NewItemPlaceHolder?

Typically in WPF, we bind collections (preferably collections that support change notification like ObservableCollection) of data objects to UI controls. Rather than adding new items to the UI controls, we add the items to the collections in the code behind/view model. As long as the collection support change notification, the UI controls will be automatically updated.
So to add a new row to your DataGrid, you need to add a new item to your collection:
dataCollection.Add(new DataType());
You should be able to access your data bound collection in your AttachedProperty using:
var dataCollection = (DataCollectionType)dataGrid.ItemsSource;
I believe that you can also use:
dataGrid.Items.Add(new DataType());
although this method is not recommended.

Related

ListView ItemsPanelTemplate with horizontal orientation, how to Identify ListViewItems on first row?

First of all I am working with MVVM / WPF / .Net Framework 4.6.1
I have a ListView configured with ItemsPanelTemplate in horizontal orientation that displays items from a DataTemplate. This setup allows me to fit as many items inside the Width of the ListView (the witdth size is the same from the Window), and behaves responsively when I resize the window.
So far everything is fine, now I just want to Identify what items are positioned on the first row, including when the window get resized and items inside the first row increase or decrease.
I merely want to accomplish this behavior because I would like to apply a different template style for those items (let's say a I bigger image or different text color).
Here below the XAML definition for the ListView:
<ListView x:Name="lv"
ItemsSource="{Binding Path = ItemsSource}"
SelectedItem="{Binding Path = SelectedItem}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Width="180" Height="35">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Grid.Row="0" Height="32" Width="32"
VerticalAlignment="Top" HorizontalAlignment="Left">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding IconPathName}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Column="1" Grid.Row="0" TextWrapping="WrapWithOverflow"
HorizontalAlignment="Left" VerticalAlignment="Top"
Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
BTW: I already did a work around where I am getting the Index from each ListViewItem and calculating against the Width of the Grid inside the DataTemplate that is a fixed value of 180, but unfortunately it did not work as I expected since I had to use a DependencyProperty to bind the ActualWidth of the of the ListView to my ViewModel and did not responded very well when I resized the window.
I know I am looking for a very particular behavior, but if anyone has any suggestions about how to deal with this I would really appreciate. Any thoughts are welcome even if you think I should be using a different control, please detail.
Thanks in advance!
You shouldn't handle the layout in any view model. If you didn't extend ListView consider to use an attached behavior (raw example):
ListBox.cs
public class ListBox : DependencyObject
{
#region IsAlternateFirstRowTemplateEnabled attached property
public static readonly DependencyProperty IsAlternateFirstRowTemplateEnabledProperty = DependencyProperty.RegisterAttached(
"IsAlternateFirstRowTemplateEnabled",
typeof(bool), typeof(ListView),
new PropertyMetadata(default(bool), ListBox.OnIsEnabledChanged));
public static void SetIsAlternateFirstRowTemplateEnabled(DependencyObject attachingElement, bool value) => attachingElement.SetValue(ListBox.IsAlternateFirstRowTemplateEnabledProperty, value);
public static bool GetIsAlternateFirstRowTemplateEnabled(DependencyObject attachingElement) => (bool)attachingElement.GetValue(ListBox.IsAlternateFirstRowTemplateEnabledProperty);
#endregion
private static void OnIsEnabledChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (!(attachingElement is System.Windows.Controls.ListBox listBox))
{
return;
}
if ((bool)e.NewValue)
{
listBox.Loaded += ListBox.Initialize;
}
else
{
listBox.SizeChanged -= ListBox.OnListBoxSizeChanged;
}
}
private static void Initialize(object sender, RoutedEventArgs e)
{
var listBox = sender as System.Windows.Controls.ListBox;
listBox.Loaded -= ListBox.Initialize;
// Check if items panel is WrapPanel
if (!listBox.TryFindVisualChildElement(out WrapPanel panel))
{
return;
}
listBox.SizeChanged += ListBox.OnListBoxSizeChanged;
ListBox.ApplyFirstRowDataTemplate(listBox);
}
private static void OnListBoxSizeChanged(object sender, SizeChangedEventArgs e)
{
if (!e.WidthChanged)
{
return;
}
var listBox = sender as System.Windows.Controls.ListBox;
ListBox.ApplyFirstRowDataTemplate(listBox);
}
private static void ApplyFirstRowDataTemplate(System.Windows.Controls.ListBox listBox)
{
double calculatedFirstRowWidth = 0;
var firstRowDataTemplate = listBox.Resources["FirstRowDataTemplate"] as DataTemplate;
foreach (FrameworkElement itemContainer in listBox.ItemContainerGenerator.Items
.Select(listBox.ItemContainerGenerator.ContainerFromItem).Cast<FrameworkElement>())
{
calculatedFirstRowWidth += itemContainer.ActualWidth;
if (itemContainer.TryFindVisualChildElement(out ContentPresenter contentPresenter))
{
if (calculatedFirstRowWidth > listBox.ActualWidth - listBox.Padding.Right - listBox.Padding.Left)
{
if (contentPresenter.ContentTemplate == firstRowDataTemplate)
{
// Restore the default template of previous first row items
contentPresenter.ContentTemplate = listBox.ItemTemplate;
continue;
}
break;
}
contentPresenter.ContentTemplate = firstRowDataTemplate;
}
}
}
}
Helper Extension Method
/// <summary>
/// Traverses the visual tree towards the leafs until an element with a matching element type is found.
/// </summary>
/// <typeparam name="TChild">The type the visual child must match.</typeparam>
/// <param name="parent"></param>
/// <param name="resultElement"></param>
/// <returns></returns>
public static bool TryFindVisualChildElement<TChild>(this DependencyObject parent, out TChild resultElement)
where TChild : DependencyObject
{
resultElement = null;
if (parent is Popup popup)
{
parent = popup.Child;
if (parent == null)
{
return false;
}
}
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is TChild child)
{
resultElement = child;
return true;
}
if (childElement.TryFindVisualChildElement(out resultElement))
{
return true;
}
}
return false;
}
Usage
<ListView x:Name="lv"
ListBox.IsAlternateFirstRowTemplateEnabled="True"
ItemsSource="{Binding Path = ItemsSource}"
SelectedItem="{Binding Path = SelectedItem}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.Resources>
<DataTemplate x:Key="FirstRowDataTemplate">
<!-- Draw a red border around first row items -->
<Border BorderThickness="2" BorderBrush="Red">
<Grid Width="180" Height="35">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Grid.Row="0" Height="32" Width="32"
VerticalAlignment="Top" HorizontalAlignment="Left">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding IconPathName}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Column="1" Grid.Row="0" TextWrapping="WrapWithOverflow"
HorizontalAlignment="Left" VerticalAlignment="Top"
Text="{Binding Name}" />
</Grid>
</Border>
</DataTemplate>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Width="180" Height="35">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Grid.Row="0" Height="32" Width="32"
VerticalAlignment="Top" HorizontalAlignment="Left">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding IconPathName}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Column="1" Grid.Row="0" TextWrapping="WrapWithOverflow"
HorizontalAlignment="Left" VerticalAlignment="Top"
Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Remarks
If the visual tree itself will not change for the first row, consider to add a second attached property to the ListBox class (e.g., IsFirstRowItem) which you would set on the ListBoxItems. You can then use a DataTrigger to modify the control properties to change the appearance. This will very likely increase the performance too.

Force a repaint of a custom drawn UIElement in a custom WPF control

I'm working on a custom WPF control. The main purpose of this control is to visualize thousands of graphical primitives in a scrollable area. The core part of the control's template looks like this:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ItemVisualizer}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<local:ItemAreaElement Grid.Row="0" Grid.Column="0" x:Name="PART_ItemArea" />
<ScrollBar Grid.Row="0" Grid.Column="1" x:Name="PART_ScrollBarVert" Orientation="Vertical" Maximum="100" />
<ScrollBar Grid.Row="1" Grid.Column="0" x:Name="PART_ScrollBarHorz" Orientation="Horizontal" Maximum="100" />
<Rectangle Grid.Row="1" Grid.Column="1" x:Name="PART_SizeGrip" Focusable="False" Fill="#F0F0F0" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
The ItemAreaElement is responsible for drawing the items. For simplicity, we can think that its core part looks like this:
class ItemAreaElement : FrameworkElement
{
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
for (int i = 0; i < _data.ItemCount; i++)
{
drawingContext.DrawLine(_penLine, new Point(0, i * 10), new Point(100, i * 10));
}
}
}
I need to repaint the ItemAreaElement every time when a related property in the whole ItemVisualizer control changes. However, I didn't find a way to do that in WPF. The well know trick with the Dispatcher object does not work in my case:
private static void OnItemCountPropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
ItemVisualizer vis = (ItemVisualizer)source;
vis._itemArea.Dispatcher.Invoke(delegate { }, DispatcherPriority.Render);
}
, where _itemArea is a local reference to the ItemAreaElement got in OnApplyTemplate():
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (this.Template != null)
{
_itemArea = this.Template.FindName("PART_ItemArea", this) as ItemAreaElement;
_itemArea.Grid = this;
}
}
Are there other ways to force an update of the UIElement in my construction? Or maybe, I need to redesign the whole control to make it possible?
It should usually be sufficient to specify FrameworkPropertyMetadataOptions.AffectsRender on registration of the ItemCount property:
public static readonly DependencyProperty ItemCountProperty =
DependencyProperty.Register(
"ItemCount", typeof(int), typeof(ItemVisualizer),
new FrameworkPropertyMetadata(FrameworkPropertyMetadataOptions.AffectsRender));
If that doesn't help, you could force a redraw by calling InvalidVisual() on the ItemAreaElement:
var vis = (ItemVisualizer)source;
vis._itemArea.InvalidVisual();

How to sync Ctrl+mousewheel across controls?

I have a class which handles a scrollbar PreviewMouseWheel event which zooms a timeline control, and everything works perfectly, except that I have to be over the scrollbar for the clr+mouse zoom to visually change the state of the scrollbar thumb. I have implemented the same PreviewMouseWheel event in the timeline control which has a different class for its datacontext, which zooms, but does not visually update the adjacent scrollbar control. What would be the best approach to handle the clr+mouse across the entire window?
public void ZoomScrollbar_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
FrameworkElement el;
el = (FrameworkElement)sender;
if (Keyboard.Modifiers == ModifierKeys.Control)
{
var newValue = InitialZoomValue;
if(e.Delta > 0)
{
newValue += 1;
}
else
{
newValue -= 1;
}
OnZoomScroll(newValue);
InitialZoomValue = newValue;
}
}
<Style x:Key="TimelineToolboxStyle" TargetType="{x:Type controls:TimelineToolbox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:TimelineToolbox}">
<Grid x:Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="*" />
<RowDefinition Height="10" />
</Grid.RowDefinitions>
<ScrollBar x:Name="ZoomScrollbar"
Grid.Row="1"
Minimum="1"
Maximum="12"
Value="{TemplateBinding InitialZoomValue}"
SmallChange="1"
Style="{DynamicResource ZoomScrollBarStyle}"
BorderThickness="9,0"
Width="22"
Margin="0"
BorderBrush="{x:Null}"
Background="Black"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I was able to get the mouse wheel to sync with the scrollbar by calling the scrollbar's PreviewMouseWheel event handler from code behind.
private void TimelineBand_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (Keyboard.Modifiers == ModifierKeys.Control)
{
TimeLineToolBoxControl.ZoomScrollbar_PreviewMouseWheel(sender, e);
}
}

Data binding to a TreeView using HierarchicalDataTemplate

I'm trying to get the TreeView control in my application to properly bind to a tree of objects by setting its ItemsSource and DataContext properties. The tree visualizes as expected, but the TreeViewItems data context seems to hold incorrect values.
For example, I have a tree that looks like this:
[-] Header
[-] Contents
[+] Item1
[+] Item2
properties
[+] Dictionary
[-] MetaDictionary
[+] TypeDef1
[+] TypeDef2
properties
The items are bound to the objects' Data.Name value. However, if I click any item that is a child of Header and examine it in the event handler, its DataContext.Data.Name says Header (after appropriate castings). Same thing happens with MetaDictionary and its children.
This is a shortened version of my class:
public class CFItemTreeNode
{
private CFItem data;
public CFItem Data
{
get { return data; }
set { data = value; }
}
private ObservableCollection<CFItemTreeNode> children;
public ObservableCollection<CFItemTreeNode> Children
{
//set & get as above
}
private CFItemTreeNode parent;
public CFItemTreeNode Parent
{
//set & get as above
}
}
And this is my XAML. I've been scouring SO and the net for several days and I've incorporated bits and pieces of various tutorials and questions into this Frankenstein of mine. I believe it's a problem with the hierarchical template, but that's as far as I've gotten.
<Window x:Class="SSLowLevelBrowser.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SSLowLevelBrowser"
Title="MainWindow"
Height="600"
Width="800"
MinHeight="100"
MinWidth="200"
Closing="MainWindowClosing">
<Window.Resources>
<Style x:Key="TreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
<!-- A margin of 0 px left and 2 px top -->
<Setter Property="Margin" Value="0 2" />
<Setter Property="AllowDrop" Value="true" />
<EventSetter Event="TreeViewItem.PreviewMouseLeftButtonDown" Handler="TVI_PreviewMouseLeftButtonDown" />
<EventSetter Event="TreeViewItem.PreviewMouseMove" Handler="TVI_PreviewMouseMove" />
<EventSetter Event="TreeViewItem.PreviewDrop" Handler="TVI_PreviewDrop" />
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="575*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="390*" />
<ColumnDefinition Width="390*" />
</Grid.ColumnDefinitions>
<ToolBar Name="menuBar" Grid.ColumnSpan="2" ToolBarTray.IsLocked="True">
<Button Name="buttonOpen" Click="OpenFile">Open file</Button>
</ToolBar>
<TreeView Grid.Row="1"
Grid.Column="0"
Name="treeView"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
ItemsSource="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:CFItemTreeNode}" ItemsSource="{Binding Children}">
<Grid>
<TextBlock Text="{Binding Path=Data.Name}"
MouseLeftButtonDown="TBlock_PreviewMouseLeftButtonDown"/>
<TextBox Text="{Binding Path=Data.Name, Mode=TwoWay}"
Visibility="Collapsed"
LostFocus="TBox_LostFocus"/>
</Grid>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
<TextBox Grid.Row="1" Grid.Column="1" Name="textOutput" />
</Grid>
</Window>
What am I doing wrong?
Update 1. Here are my event handlers:
private void TVI_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
{
dragStartPosition = args.GetPosition(null);
dragSource = args.OriginalSource;
}
private void TVI_PreviewMouseMove(object sender, MouseEventArgs args)
{
Point currentPosition = args.GetPosition(null);
// If there is actual movement and a drag is starting
if (dragInProgress == false &&
dragStartPosition.X != -1 &&
args.LeftButton == MouseButtonState.Pressed &&
Math.Pow(currentPosition.X - dragStartPosition.X, 2) +
Math.Pow(currentPosition.Y - dragStartPosition.Y, 2) > 25)
{
dragInProgress = true;
DragDropEffects de = DragDrop.DoDragDrop(
(TreeViewItem)sender,
new DataObject(typeof(FrameworkElement), dragSource),
DragDropEffects.Move);
}
}
private void TVI_PreviewDrop(object sender, DragEventArgs args)
{
if (dragInProgress && args.Data.GetDataPresent(typeof(FrameworkElement)))
{
CFItemTreeNode dragSource =
((CFItemTreeNode)((FrameworkElement)args.Data.GetData(typeof(FrameworkElement))).DataContext);
CFItemTreeNode dropTarget =
((CFItemTreeNode)((FrameworkElement)args.OriginalSource).DataContext);
CFItemTreeNode sourceParent = dragSource.Parent;
CFItemTreeNode targetParent = dropTarget.Parent;
if (sourceParent != targetParent)
{
MessageBox.Show("Can only swap siblings.");
dragInProgress = false;
return;
}
int sourceIndex = sourceParent.Children.IndexOf(dragSource);
int targetIndex = sourceParent.Children.IndexOf(dropTarget);
if (sourceIndex != targetIndex)
{
if (sourceIndex < targetIndex)
{
sourceParent.Children.RemoveAt(targetIndex);
sourceParent.Children.RemoveAt(sourceIndex);
sourceParent.Children.Insert(sourceIndex, dropTarget);
sourceParent.Children.Insert(targetIndex, dragSource);
}
else
{
sourceParent.Children.RemoveAt(sourceIndex);
sourceParent.Children.RemoveAt(targetIndex);
sourceParent.Children.Insert(targetIndex, dragSource);
sourceParent.Children.Insert(sourceIndex, dropTarget);
}
}
dragSource = null;
dragInProgress = false;
// Reset start position to invalid
dragStartPosition = new Point(-1, -1);
}
}
Add RelativeSource={RelativeSource AncestorType=local:MainWindow} to your binding.

Strange behaviour with TextBox.Foreground.Opacity property

I created a silverlight template control. Thouse control consist 4 elements: 2 textbox and 2 textblock.
markup (in generic.xaml):
<Style TargetType="local:InputForm">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:InputForm">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Login" Grid.Column="0" Grid.Row="0"/>
<TextBlock Text="Password" Grid.Column="0" Grid.Row="1"/>
<TextBox x:Name="LoginTextBox" Grid.Column="1" Grid.Row="0" Text="Login..."/>
<TextBox x:Name="PasswordTextBox" Grid.Column="1" Grid.Row="1" Text="Password..."/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In code file I get the textbox from template and set Foreground.Opacity property equels 0.5.
code:
public class InputForm : Control
{
private TextBox _loginTextBox;
private TextBox _passwordTextBox;
public InputForm()
{
this.DefaultStyleKey = typeof(InputForm);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_loginTextBox = this.GetTemplateChild("LoginTextBox") as TextBox;
_passwordTextBox = this.GetTemplateChild("PasswordTextBox") as TextBox;
SetInActive();
}
private void SetInActive()
{
_loginTextBox.Foreground.Opacity = .5;
_passwordTextBox.Foreground.Opacity = .5;
}
}
When I added this control in my silverlight application all textboxs element began represent text with Foreground.Opacity = 0.5
Start application:
Select "Login" tab:
Back to "Some infromation" tab:
Sample located here: http://perpetuumsoft.com/Support/silverlight/SilverlightApplicationOpacity.zip
Is it silverlight bug or I do something wrong?
The problem is that the Foreground property is of type Brush which is a reference type (a class).
When you assign .Opacity = 0.5 you are changing the opacity value of the referenced Brush. All other elements that are referencing the same brush will be affected.
Ordinarily we would use a Storyboard in VisualStateManager in the control template to specify the visual appearance of a control in different "states".
However a quick fix for your code would be:
private void SetInActive()
{
Brush brush = new SolidColorBrush(Colors.Black) { Opacity = 0.5 };
_loginTextBox.Foreground = brush
_passwordTextBox.Foreground= brush
}

Resources