Setting margin to controls outside Grid with reference to control inside Grid - wpf

Working on wpf solution
i have created a Grid with different columns, a control is added in column 2.
Now i have a control outside grid and the Left margin should be same as the control inside the grid.
Can this be done?

We can of course take the very simple way of changing Margin using code.
For pure MVVM approach, you can do that using AttachedProperty.
Binding with a Convertor won't work here as Thickness type is not a DependencyObject.
If we simply bind the Margin of outer Button to inner Button, Entire Margin of outer Button will change, which we dont want. So, we need to preserve whole Margin except Left Margin. Left Margin can be changed using Binding. But how ? Our outer Button needs to have two Margin values, one original, and another coming from inner Button, so that original one can be changed. For this another margin, we can take help of Attached Property, as they allow us to extend a control.
AttachedProperty
public static BindingExpression GetLefMargin(DependencyObject obj)
{
return (BindingExpression)obj.GetValue(LefMarginProperty);
}
public static void SetLefMargin(DependencyObject obj, BindingExpression value)
{
obj.SetValue(LefMarginProperty, value);
}
// Using a DependencyProperty as the backing store for LefMargin. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LefMarginProperty =
DependencyProperty.RegisterAttached("LefMargin", typeof(BindingExpression), typeof(Window1), new PropertyMetadata(null, new PropertyChangedCallback(MarginCallback)));
private static void MarginCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement elem = d as FrameworkElement;
BindingExpression exp = e.NewValue as BindingExpression;
// Create a new Binding to set ConverterParameter //
Binding b = new Binding();
b.Converter = exp.ParentBinding.Converter;
b.ConverterParameter = elem.Margin;
b.Path = exp.ParentBinding.Path;
b.ElementName = exp.ParentBinding.ElementName;
b.Mode = exp.ParentBinding.Mode;
elem.SetBinding(FrameworkElement.MarginProperty, b);
}
Converter
public class LeftMarginCnv : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double gridCtrlLeftMargin = ((Thickness)value).Left;
Thickness tgtCtrlMargin = (Thickness)parameter;
return new Thickness(gridCtrlLeftMargin, tgtCtrlMargin.Top, tgtCtrlMargin.Right, tgtCtrlMargin.Bottom);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Usage
<Grid>
<Grid Background="Red" Margin="29,55,52,125" ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90*"/>
<ColumnDefinition Width="121*"/>
</Grid.ColumnDefinitions>
<Button x:Name="Btn" Content="Press" Margin="18,22,35,28" Grid.Column="1" Click="Btn_Click"/>
</Grid>
<Button local:Window1.LefMargin="{Binding Margin, ElementName=Btn, Converter={StaticResource LeftMarginCnvKey}}" Content="Button" HorizontalAlignment="Left" Margin="55,199,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
Outer Button will change its Left Margin if you change the Left margin of inner Button.

You can set the common style for both. Something like this.
<StackPanel>
<StackPanel.Resources>
<Style x:Key="commonstyle" TargetType="{x:Type FrameworkElement}">
<Setter Property="Margin" Value="10,0,0,0" />
</Style>
</StackPanel.Resources>
<TextBox x:Name="outside" Width="100" Height="70" Style="{StaticResource commonstyle}"/>
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Width="100" Height="70" x:Name="inside" Grid.Column="2" HorizontalAlignment="Left" Style="{StaticResource commonstyle}"/>
</Grid>
</StackPanel>
(or)
Make it simple
<StackPanel>
<TextBox x:Name="outside" Width="100" Height="70" Margin="{Binding ElementName=inside, Path=Margin}"/>
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Width="100" Height="70" x:Name="inside" Grid.Column="2" HorizontalAlignment="Left" Margin="20"/>
</Grid>
</StackPanel>
Hope that helps.

Related

WPF custom control not working as expected in Listview

I want to put an editabletextblock cutom control in a Listview datatemplate. I'm following this article and it works well.
But when i'm put this control in a Listview datatemplate, on double click on the Textblock, the event OnMouseDoubleClick of the custom control is fired but the Textbox is never display.
My Datatemplate :
<DataTemplate x:Key="ItemTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal"
Grid.Column="0">
<Image Source="{Binding Icon}"
Margin="0 0 4 0" />
<localp:EditableTextBlock Text="{Binding Tag, Mode=TwoWay}"
VerticalAlignment="Center" />
</StackPanel>
</Grid>
<ListView
ItemTemplate={StaticResource ItemTemplate}
.... />
And i don't know why the OnMouseDoubleClick EditableTextBlock is fired but the inner Textbox is never displayed as expected.
Thanks is advance for your help,
Regards
Change the default values of TextBlockForegroundColorProperty and TextBoxForegroundColorProperty from null to something else:
public static readonly DependencyProperty TextBlockForegroundColorProperty =
DependencyProperty.Register("TextBlockForegroundColor",
typeof(Brush), typeof(EditableTextBlock), new UIPropertyMetadata(Brushes.Black));
public static readonly DependencyProperty TextBoxForegroundColorProperty =
DependencyProperty.Register("TextBoxForegroundColor",
typeof(Brush), typeof(EditableTextBlock), new UIPropertyMetadata(Brushes.Black));
Or set them in you Xaml:
<local:EditableTextBlock TextBlockForegroundColor="Black" TextBoxForegroundColor="Black" ... />
Edit
you can set keyboard focus to the TextBox, however, you should set e.Handled to true, or the OnTextBoxLostFocus will execute and hides your TextBox.
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
this.m_TextBlockDisplayText.Visibility = Visibility.Hidden;
this.m_TextBoxEditText.Visibility = Visibility.Visible;
if (m_TextBoxEditText.IsKeyboardFocusWithin ==false)
{
Keyboard.Focus(m_TextBoxEditText);
e.Handled = true;
m_TextBoxEditText.CaretIndex = m_TextBoxEditText.Text.Length;
}
}

Texttrimming in Auto grid column

I want something very simple in WPF, but I don't get it to work:
I have a grid with 2 columns: one * and one Auto. The second column contains a TextBlock. I need texttrimming to work with this TextBlock. This doesn't work currently, because the TextBlock goes outside the bounds of the grid.
Extra info:
The second column should be juste wide enough to contain the TextBlock. The first column should contain all remaining space. If the Grid isn't wide enough to contain the desired width of the TextBlock, the text should be trimmed.
The width of the Grid changes when resizing the window.
Nothing is static (not the text, no sizes), so hardcoded values can not be used.
ClipToBounds property doesn't fix this issue.
I can't bind MaxWidth of the TextBlock to the width of the column, otherwise the TextBlock will only getting smaller, but never bigger when resizing the window.
Code to reproduce the issue (for example in Kaxaml):
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<Grid Height="20" Background="Blue" DockPanel.Dock="Top" Margin="100 0 100 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" MaxWidth="200"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="1"
Background="Red"
Text="Test tralalalalalalalalalala long string this should be trimmed!"
TextTrimming="CharacterEllipsis"/>
</Grid>
</DockPanel>
</Page>
Any suggestions?
Second solution:
Use a Converter like this:
namespace StackStuff{
class WidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is Double)
{
return (double)value - 200; // 200 = 100+100 form the grid margin
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
In the View, you will have:
xmlns:local="clr-namespace:StackStuff"
Then, you have to add the converter for it to be used:
<Window.Resources>
<local:WidthConverter x:Key="WidthConverter"/>
</Window.Resources>
And then you have to implement the converter:
<DockPanel Background="Green" x:Name="dock">
<Grid Height="20" Background="Blue" DockPanel.Dock="Top" Margin="100 0 100 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock MaxWidth="{Binding ActualWidth, Converter={StaticResource WidthConverter}, ElementName=dock}"
Grid.Column="1"
Hope this is what you wanted.

Get height of header of a ListView with GridView

How can I get the height of the header of a ListView with a GridView? Is it even possible?
I need to set the height of one control based on the height of the header of a ListView so I started writing a Converter. The problem is that I can't access the actual height.
The debugger of Visual Studio shows me that GridView has a property called HeaderRowPresenter, which in turn has a property ActualHeight. But I can't access it, HeaderRowPresenter seems to be protected or private.
All other ColumnHeader* properties (ColumnHeaderContainerStyle, ColumnHeaderTemplate, etc.) are null on this object, same for all Header* properties on the Columns (except for the String that is the content of the header).
Btw: I'm trying to solve a different problem and my current approach let me to this, so maybe I'm taking this on the wrong way.
Okay, I've found a solution but I'm not very proud of it, because it uses reflection:
I wrote a Converter that takes a View and a ListView as value and parameter and returns the height difference of the headers:
public class HeightSyncConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
GridView gvLocal = value as GridView;
ListView lvOther = parameter as ListView;
if( gvLocal == null || lvOther == null)
{
return 0;
}
GridView gvOther = (GridView)lvOther.View;
try
{
// get the non-public HeaderRowPresenter property
GridViewHeaderRowPresenter hrpLocal = (GridViewHeaderRowPresenter)gvLocal.GetType().GetProperty("HeaderRowPresenter", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(gvLocal);
GridViewHeaderRowPresenter hrpOther = (GridViewHeaderRowPresenter)gvOther.GetType().GetProperty("HeaderRowPresenter", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(gvOther);
if( hrpLocal == null || hrpOther == null )
{
return 0;
}
// Only works if the other ListView's header is higher than the local one's
if( hrpLocal.ActualHeight > hrpOther.ActualHeight )
{
return 0;
}
return hrpOther.ActualHeight - hrpLocal.ActualHeight;
}
catch(TargetInvocationException) { }
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
I then use it to define the height of a Grid.Row:
<UserControl x:Class="GUI.ListViewLayout"
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:local="clr-namespace:GUI"
xmlns:conv="clr-namespace:GUI.Converter"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"
Loaded="Window_Loaded">
<UserControl.Resources>
<Style x:Key="verticalGridViewColumnHeader" TargetType="GridViewColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" FontWeight="Bold"
VerticalAlignment="Center" TextAlignment="Center" HorizontalAlignment="Center">
<TextBlock.LayoutTransform>
<RotateTransform Angle="270" />
</TextBlock.LayoutTransform>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<conv:HeightSyncConverter x:Key="myConverter" />
</UserControl.Resources>
<Grid x:Name="grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition x:Name="fillerRow" Height="{Binding View, ElementName=listView1, Converter={StaticResource myConverter}, ConverterParameter={x:Reference listView2}}" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button x:Name="someControl" Grid.RowSpan="2">Placeholder</Button>
<ListView x:Name="listView1" Grid.Row="2">
<ListView.View>
<GridView>
<GridViewColumn Header="Header 1" Width="60"/>
<GridViewColumn Header="Header 2" Width="60" />
</GridView>
</ListView.View>
</ListView>
<ListView x:Name="listView2" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2">
<ListView.View>
<GridView>
<GridViewColumn Header="Long Header 1"
HeaderContainerStyle="{StaticResource verticalGridViewColumnHeader}" Width="Auto" />
<GridViewColumn Header="Long Header 2"
HeaderContainerStyle="{StaticResource verticalGridViewColumnHeader}" Width="Auto" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>
The problem is that the converter is called before the ListViews and their headers are initialized, so I reevaluate the Binding in the code-behind:
public partial class ListViewLayout : UserControl
{
public ListViewLayout()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
BindingExpression binding = fillerRow.GetBindingExpression(RowDefinition.HeightProperty);
binding.UpdateTarget();
}
}
All in all, I don't think this is worth the effort and the best way is to define the height of the headers myself instead of retrieving an automatically calculated ActualHeight.

Set focus to 1st textbox in items control

Let's say I have an items control that is bound to a list of items on the VM. Inside the datatemplate is a textbox. How would I set focus to the first textbox in either XAML or the VM?
Thanks in advance for any help!
<ItemsControl ItemsSource="{Binding UsageItems}" Grid.Row="1" Focusable="False">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Month}" Style="{StaticResource Local_MonthLabel}" />
<core:NumericTextBox Value="{Binding Actual, Mode=OneWay}" Style="{StaticResource Local_ActualUsageEntry}" Grid.Column="2"/>
<core:ValidationControl Instance="{Binding Model}" Grid.Column="4" PropertyName="{Binding MonthNumber, StringFormat=AdjustedUsage{0}}">
<core:NumericTextBox Value="{Binding Adjusted}" DefaultValueIfNull="0" Style="{StaticResource Local_AdjustUsageEntry}" x:Name="AdjustmentEntry" inventoryLocationSetup:InitialFocusBehavior.Focus="True" />
</core:ValidationControl>
<telerik:RadComboBox ItemsSource="{Binding Converter={StaticResource Converter_EnumToEnumMemberViewModel}, Mode=OneTime, Source={x:Type Enums:UsageAdjustmentTypes}}" SelectedValue="{Binding Code, Mode=TwoWay}" Grid.Column="6" Style="{StaticResource Local_CodeSelector}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
I use an attached behaviour:
public static class InitialFocusBehavior
{
public static bool GetFocus(DependencyObject element)
{
return (bool)element.GetValue(FocusProperty);
}
public static void SetFocus(DependencyObject element, bool value)
{
element.SetValue(FocusProperty, value);
}
public static readonly DependencyProperty FocusProperty =
DependencyProperty.RegisterAttached(
"Focus",
typeof(bool),
typeof(InitialFocusBehavior),
new UIPropertyMetadata(false, OnElementFocused));
static void OnElementFocused(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = depObj as FrameworkElement;
if (element == null)
return;
element.Focus();
}
}
Then in the XAML bind it to True for the element you want focused:
<TextBox Width="200" Height="20" local:InitialFocusBehavior.Focus="True" />
=== UPDATE ===
Sorry, the code above just shows how to use a behaviour to give focus to a control on the page, if you want to do it to an element in the first item in an ItemControl then you'll have to instead apply the behavior to the ItemsControl itself and then in your update handler find the child by doing something like this instead:
static void OnElementFocused(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ItemsControl itemsControl = depObj as ItemsControl;
if (itemsControl == null)
return;
itemsControl.Loaded += (object sender, RoutedEventArgs args) =>
{
// get the content presented for the first listbox element
var contentPresenter = (ContentPresenter)itemsControl.ItemContainerGenerator.ContainerFromIndex(0);
// get the textbox and give it focus
var textbox = contentPresenter.ContentTemplate.FindName("myTextBox", contentPresenter) as TextBox;
textbox.Focus();
};
}
You'll notice that I'm setting the focus inside the OnLoaded handler because I'm assuming that the items won't have been attached yet when the control is first created.
Also you've probably already figured it out by "local" is just the namespace that the InitialFocusBehavior class is defined in, you'll need to add something like this at the top of the xaml:
xmlns:local="clr-namespace:YourProjectNamespace"

WPF binding IValueConverter and the width of another control

The following code worked fine. There was an error elsewhere in the code. Still, the advice given is good.
I am trying to bind the Width of a TextBox to a percentage of the Width of a parent control. I know I can accomplish something similar by simply setting a Margin, but I was wondering why this doesn't work.
First, I set a reference to an IValueConverter in the resources collection of my user control:
<UserControl.Resources>
<local:TextBoxWidthConverter x:Key="txtWidthConv" />
</UserControl.Resources>
In the main xaml, I have the following:
<StackPanel Name="parentPanel" Width="300">
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Hidden" Name="scroller" Width="{Binding Width,
ElementName=parentPanel, Converter={StaticResource txtWidthConv}}">
<StackPanel Orientation="Horizontal">
<TextBox></TextBox>
</StackPanel>
</ScrollViewer>
</StackPanel>
The ivalueconverter looks like this:
public class TextBoxWidthConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double result = (double)value;
if (!Double.IsNaN(result))
{
result = result * .25;
}
else
{
result = 100D;
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new System.NotImplementedException("Not implemented.");
}
#endregion
}
Setting the width property does nothing here, let alone setting the IValueConverter. I would expect the ScrollViewer to be 1/4 the width of the parent StackPanel.
Set the ScrollViewer's HorizontalAlignment to something other than Stretch.
Also, you should bind to the ActualWidth property.
Let the layout system work for you instead of fighting it. Grid will automatically handle relative sizing:
<StackPanel Name="parentPanel" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Hidden" Name="scroller">
<StackPanel Orientation="Horizontal">
<TextBox></TextBox>
</StackPanel>
</ScrollViewer>
</Grid>
</StackPanel>

Resources