Behavior<T> AssociatedObject does not work correctly - wpf

I have a class implemnting Behavior<FrameworkElement> with
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.AllowDrop = true;
AssociatedObject.DragEnter += AssociatedObject_DragEnter;
AssociatedObject.DragOver += AssociatedObject_DragOver;
AssociatedObject.Drop += AssociatedObject_Drop;
}
And in the xaml I have
<Border Background="Turquoise">
<Grid Height="800" AllowDrop="True">
<i:Interaction.Behaviors>
<behaviors1:FrameworkElementDropBehavior></behaviors1:FrameworkElementDropBehavior>
</i:Interaction.Behaviors>
...
</Grid>
</Border>
I've defined the FrameworkElementDropBehavior in the Grid and I expect I can drop the same object on this Grid because the AssociatedObject should be the whole Grid. But what happens is I am only allowed to drop on part of the Grid where there's element defined, such as the blue, white or value part. I do used prism to inject the whole green Grid into the TabControl. Any ideas why I can only drop partly?

Just set the Grid's background property, for example, to Transparent.
<Grid Height="800" AllowDrop="True" Background="Transparent">
<i:Interaction.Behaviors>
<behaviors1:FrameworkElementDropBehavior/>
</i:Interaction.Behaviors>
</Grid>
Doing so, you enable the hit testing on the whole grid area including any empty regions without child controls.

I fixed the problem by setting the FrameworkElementDropBehavior to the Border instead .
<Border Background="Turquoise">
<i:Interaction.Behaviors>
<behaviors1:FrameworkElementDropBehavior></behaviors1:FrameworkElementDropBehavior>
</i:Interaction.Behaviors>
<Grid Height="800" AllowDrop="True">
...
</Grid>
</Border>

Related

Prevent a TextBox from horizontal expanding in WPF

I have the following style defined in my App.xaml
<Style x:Key="textBoxMultiline" TargetType="{x:Type TextBox}" >
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="HorizontalScrollBarVisibility" Value="Hidden" />
<Setter Property="MinHeight" Value="50" />
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
And throughout the solution we're using it on every text box that needs a brief text.
<TextBox x:Name="textBoxDescription" Grid.Row="2" Grid.Column="1" Style="{DynamicResource textBoxMultiline}" />
Everything works great, but then the client complains about some fields were corped on older monitors with lower resolutions, so I placed a ScrollViewer on one of the higher visual tree nodes to prevent the corping.
<ScrollViewer Height="Auto" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
...
</ScrollViewer>
Strangely, the TextBoxes with the above style start expanding to the right instead of wrapping the text.
Is there a way to prevent this without removing the ScrollViewer?
If you don't want to hard code the width then you can go for element binding the width of the parent item.
Here I am binding TextBox MaxWidth with ScrollViewer actual width. You also have to make sure that the ColumnDefinition width should be set to "*" not to "Auto". If you set it to Auto it will neglect the ScrollViewer width and keep on expanding the width of ScrollViewer and TextBox. I think you fall in this case...
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto" Name="scv">
<TextBox Height="30" TextWrapping="Wrap" MaxWidth="{Binding ElementName=scv, Path=ActualWidth}"></TextBox>
</ScrollViewer>
</Grid>
You must define a MaxWidth for the TextBox, otherwise there's no limit because the ScrollViewer.
The solution provided from #bathineni helped me solve my problem. Here is what worked for me:
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Width="30" Height="23" Margin="10,5" Content="..."/>
<ScrollViewer Grid.Column="1" HorizontalScrollBarVisibility="Disabled" verticalScrollBarVisibility="Disabled" Name="scv">
<TextBox Height="25" Text="Insert here long text" MaxWidth="{Binding ElementName=scv, Path=ActualWidth}" HorizontalAlignment="Stretch" />
</ScrollViewer>
</Grid>
I tried the aforementioned examples and they didn't work so, I solved the problem myself. There are two ways of solving this issue:
The first solution is implemented in XAML using data bindings. I advice you not to bind the control by itself. The XAML solution is implemented by binding a control with the desired ActualWidth and ActualHeight proprieties to the textbox MaxHeight and MaxWidth proprieties.
<TextBlock x:Name="PasswordText" Margin="0,0,0,20" FontFamily="Bahnschrift SemiBold Condensed" Text="PASSWORD" FontSize="20">
<TextBox x:Name="PasswordTextBox" MaxWidth="{Binding ElementName=PasswordText, Path=ActualWidth}" MaxHeight="{Binding ElementName=PasswordText, Path=ActualHeight}">
The next solution is implemented by generating a Loaded event in XAML, creating it in the C# code and then setting within the Loaded event the MaxWidth and MaxHeight proprieties of the textbox as the textbox ActualWidth and ActualHeight proprieties.
// It doesn't have a problem like in XAML if you pass the textbox its own
// ActualWidth and ActualHeight to the MaxWidth and MaxHeight proprieties.
private void Log_In_Page_Loaded(object sender, RoutedEventArgs e)
{
UsernameTextBox.MaxHeight = UsernameTextBox.ActualHeight;
UsernameTextBox.MaxWidth = UsernameTextBox.ActualWidth;
}
Choose the one that suits your design better, but I think, in my opinion, that this is the most effective, simple, stable and effective way of solving this problem.
Works for me. If you want scrollbars to appear in the textbox, you may add
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
to the TextBox
You must set MaxWidth of the Container Control
<Grid x:Name="RootGrid" Margin="6,6,8,8" Width="500" MaxWidth="500">
<ScrollViewer ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Name="contentGroup" Header="Content" Grid.Row="0">
<TextBox Name="content"/>
</GroupBox>
</Grid>
</ScrollViewer>
I ran into this problem when I needed my TextBox to stretch along with its auto-sized Grid column when it got resized, meaning MaxWidth wouldn't work, but still needed to prevent the TextBox from stretching along with its contents.
What I ended up doing was linking this event handler to SizeChanged:
private void TextBox_SizeChanged(object sender, SizeChangedEventArgs e) {
TextBox textBox = (TextBox)sender;
if (textBox.CanUndo && e.NewSize.Width > e.PreviousSize.Width) {
textBox.Width = e.PreviousSize.Width;
}
}
Writing text to a textbox is something that the user can Undo, whereas other actions that can cause resizing (initial drawing of the element, stretching of parent container, etc) aren't Undoable from within the textbox. Thus, by checking CanUndo we can determine whether SizeChanged was triggered by writing text or something else.
The e.NewSize.Width > e.PreviousSize.Width check is necessary because without it the SizeChanged event will infinitely be called from within itself, because to revert the stretching we need to change the size back to the original, which would itself trigger the event.
It's a little hacky but I haven't run into any issues yet.
I am not sure why but I could not get the ScrollViewer solution to work. I needed to have a TextBox with a fixed initial width to implement a numeric up/down control - in this control the TextBox was shrinking and growing independent of the input which looks very annoying if the UI changes as you type.
So, I found the below solution using 2 textboxes to work for me. The first textbox is the textbox displayed for the user to type their input and the 2nd textbox is initialized through a dependency property (DisplayLength) and the converter shown further below.
Binding the MaxWidth property of the 1st TextBox to the Width property of the 2nd TextBox fixes the size such that users can type what they want but the displayed width of the textbox will not change even if there is more UI space available.
<TextBox x:Name="PART_TextBox"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value}"
Margin="0,0,1,0"
TextAlignment="Right"
AcceptsReturn="False"
SpellCheck.IsEnabled="False"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Center"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
MaxWidth="{Binding ElementName=TBMeasure, Path=ActualWidth}"
/>
<!-- Hidden measuring textbox ensures reservation of enough UI space
according to DisplayLength dependency property
-->
<TextBox x:Name="TBMeasure"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DisplayLength, Converter={StaticResource ByteToPlaceHolderStringConverter}}"
Margin="0,0,1,0"
TextAlignment="Right"
AcceptsReturn="False"
SpellCheck.IsEnabled="False"
HorizontalContentAlignment="Right"
VerticalContentAlignment="Center"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Visibility="Hidden"/>
// Converter
[ValueConversion(typeof(byte), typeof(string))]
public sealed class ByteToPlaceHolderStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((value is byte) == false)
return Binding.DoNothing;
byte byteVal = (byte)value;
string retString = string.Empty;
for (int i = 0; i < byteVal; i++)
retString = retString + "X";
return retString;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}

Minimum resizing

Inside a grid have a ContentControl inside a Grid where I load a UserControl.
I want the user to resize the window, but how can I prevent resizing-down the window so it will be less the the user control?
In other words, user control should be always visible on the window.
<Grid>
<Border>
<ContentControl Content="{Binding Path=THeModel}">
</ContentControl>
</Border>
</Grid>
Use the MinWidth and MinHeight properties of the Window to set a minimum width and height.
<Window MinWidth="200" MinHeight="200" ... > ... </Window>
If it depends on its content, you can try binding these properties to the ActualWidth/ActualHeight of another control:
<Window MinWidth="{Binding ElementName=MyControl, Path=ActualWidth}" ... > ... </Window>
But this will only work well if MyControl has a fixed size - if it grows with the window, then the results will not be ideal.

Basic WPF Layout question

I am learning WPF and am trying to follow some sort of best practice. I am kind of lost at the moment, and need some direction.
I am creating a very simple app that reads a text file (error log) and splits it into the individual error messages. I want to display these messages (stored in a model object) as a list of messages. Since the list can consist of many items and I want the window to be resizeable, I need a a vertical scroll bar, but I want the content to wrap (i.e. no need for a horizontal scroll bar).
<Window x:Class="ErrorLog.UI.WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="800" Width="1200" Loaded="Window_Loaded">
<StackPanel Name="mainContainer">
<StackPanel Orientation="Horizontal" Name="Menu">
<Button Name="Refresh">Refresh</Button>
</StackPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Name="errorMessagePlaceHolder"></StackPanel>
</ScrollViewer>
</StackPanel>
I am at the moment reading the file in the code behind and adding to the stackPanel as a bunch of textboxes with the value being the error message. I have also added some mouseover effects like this:
private void LoadData()
{
IErrorLogReader errorLogReader = new ErrorLogReader();
var errors = errorLogReader.RetrieveErrors();
if (errors.Count == 0)
{
TextBox noErrors = new TextBox();
noErrors.Text = "No errors found";
errorMessagePlaceHolder.Children.Add(noErrors);
}
else
{
for (var i = errors.Count - 1; i > 0; i--)
{
TextBox errorMessage = new TextBox();
errorMessage.IsReadOnly = true;
errorMessage.Padding = new Thickness(10);
errorMessage.Text = errors[i].ErrorMessage;
errorMessage.TextWrapping = TextWrapping.Wrap;
errorMessage.MouseEnter += ErrorMessageMouseEnter;
errorMessage.MouseLeave += ErrorMessageMouseLeave;
errorMessagePlaceHolder.Children.Add(errorMessage);
}
}
}
protected void ErrorMessageMouseEnter(object sender, RoutedEventArgs e)
{
((TextBox) sender).Background = Brushes.AntiqueWhite;
}
protected void ErrorMessageMouseLeave(object sender, RoutedEventArgs e)
{
((TextBox) sender).Background = null;
}
So the first things I want to know is:
Is the way I am binding ok?
Scroll bar is coming up disabled
Is the way I am doing the mouseover effect bad?
Cheers.
Is the way I am binding ok?
It might work, but it's not best practice. Best practice is to use actual data binding. First, you need to replace your StackPanel with something that can be bound to a list. An ItemsControl is the thing closest to a simple StackPanel, other options would be, for example, a ListBox.
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl Name="errorMessageList" />
</ScrollViewer>
private void LoadData()
{
IErrorLogReader errorLogReader = new ErrorLogReader();
var errors = errorLogReader.RetrieveErrors();
errorMessageList.ItemsSource = errors;
}
To specify how you want the error messages to be displayed, you can set a template for the ItemsControl:
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl Name="errorMessageList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox IsReadOnly="true" ... Text="{Binding ErrorMessage}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
Scroll bar is coming up disabled
You are nesting your ScrollViewer inside a StackPanel... that won't work: The StackPanel takes as much vertical space as it needs, so the ScrollViewer will always have enough space and never show the scroll bar. You need to replace your top-level StackPanel by something that takes only as much space as is available; a DockPanel, for example:
<DockPanel Name="mainContainer">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Name="Menu">
<Button Name="Refresh">Refresh</Button>
</StackPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Name="errorMessagePlaceHolder"></StackPanel>
</ScrollViewer>
</StackPanel>
Is the way I am doing the mouseover effect bad?
That can be done with a style and a trigger instead. Define the following style:
<Window ...>
<Window.Resources>
<Style x:Key="hoverTextBox" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="AntiqueWhite" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
...
</Window>
and assign it to your TextBox inside the DataTemplate:
<TextBox IsReadOnly="true" ... Text="{Binding ErrorMessage}"
Style="{StaticResource hoverTextBox}" />

cloning the controls

I am designing a silverlight application in which i will have a rectangle control at the left side, when i click the rectangel and drag a copy of the rectangle control should be created and dragged and dropped in to the page.
Please can anyone help me with the code
For simplicity I'm going to leave out the Drag-Drop stuff since this question seems mainly about the cloning aspect.
The tool needed is the DataTemplate class. You place in a resource dictionary the set of items you want to clone each enclosed in a DataTemplate. You can use ContentPresenter to display instances of these items in say stack panel on the left. You can then use code to create instances of the template content and place them in say a Canvas on the right.
Example.
Xaml:-
<UserControl x:Class="SilverlightApplication1.CloningStuff"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<UserControl.Resources>
<DataTemplate x:Key="Rectangle">
<Rectangle Stroke="Blue" StrokeThickness="3" Fill="CornflowerBlue" Width="100" Height="75" />
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel>
<ContentPresenter x:Name="Rectangle" ContentTemplate="{StaticResource Rectangle}" />
</StackPanel>
<Canvas x:Name="Surface" MouseLeftButtonDown="Surface_MouseLeftButtonDown" Grid.Column="1" Background="Wheat">
</Canvas>
</Grid>
</UserControl>
Code:-
public partial class CloningStuff : UserControl
{
public CloningStuff()
{
InitializeComponent();
}
private void Surface_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Canvas target = (Canvas)sender;
Point p = e.GetPosition(target);
Rectangle r = (Rectangle)((DataTemplate)Resources["Rectangle"]).LoadContent();
Canvas.SetLeft(r, p.X);
Canvas.SetTop(r, p.Y);
target.Children.Add(r);
}
}
This shows using a ContentPresenter to display your rectangle. In place of drag-dropping (for which there are plenty of examples of elsewhere) this code just creates a Clone of the rectangle whereever the user clicks in the Canvas.

WPF: GroupBox dynamic height

I have a textbox and datagrid inside of a dockpanel that is in a WPF groupbox.
<GroupBox Margin="8,142.04,1.783,230.4" Height="Auto" Header="Desired Meeting Outcomes (decisions or actions)?" MaxWidth="635" MinWidth="550" FontWeight="Bold" FontSize="13.333" BorderBrush="#FFD5DFE5" MinHeight="106" VerticalContentAlignment="Stretch">
<DockPanel Margin="0">
<local:TextboxControl Margin="0" d:LayoutOverrides="Height, HorizontalMargin" Width="538.217" VerticalAlignment="Top" HorizontalAlignment="Left" DockPanel.Dock="Top"/>
<local: Height="Auto" HorizontalAlignment="Left" MinHeight="25" MinWidth="538" DockPanel.Dock="Top"/>
</DockPanel>
</GroupBox>
I am adding rows in the datagrid dynmaically from the textbox causing the datagrid to grow. However, my groupbox's height is not growing dynamically even though its height is set to Auto. How can I get my groupbox to grow and shrink based upon the size of the contents that it holds?
You have margins set on all 4 sides with a VerticalAlignment of Stretch. In a Grid this will basically give you a GroupBox that sizes with its parent but not its content. Remove the margin from the right and bottom and change the VerticalAlignment to Top.
The margins are the order of L, T, R, B. So zero out the last two. Height=Auto and VerticalContentAlignment=Stretch are the defaults so you can get rid of those too. Try to keep the XAML as clean as possible.
It's clear from the markup that you're using Blend or Visual Studio's designer. I would suggest using the designer for "preview" mode rather than editing. Although it's gotten much better I find the layout behavior of the designer in both products to be very frustrating. Getting familiar with creating XAML by hand pays dividends in the long run.
EXAMPLE
As per the comments, I'm adding an example of how you would have a DataGrid that causes its parent elements to automatically grow based on height. Notice that only the Window itself has a fixed size. For a Window, if you wanted to make it grow based on height you could set SizeToContent=Height. Notice how you only need to set VerticalAlignment=Top on the outermost element.
MainWindow.xaml
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="640" Height="480">
<Grid x:Name="LayoutRoot" Background="Green" VerticalAlignment="Top">
<Border Margin="5" BorderBrush="Yellow" BorderThickness="4">
<GroupBox Header="Data Grid" Background="Orange">
<DataGrid x:Name="dg" AutoGenerateColumns="True" />
</GroupBox>
</Border>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow( )
{
InitializeComponent( );
var items = new ObservableCollection<DateTime>( );
dg.ItemsSource = items;
var timer = new DispatcherTimer( );
timer.Interval = TimeSpan.FromSeconds( 2 );
timer.Tick += ( s, e ) => items.Add( DateTime.Now );
timer.Start( );
}
}
What is the container of the GroupBox ? It could prevent it from growing.
For example, if the container is the Windows, does it have SizeToContent="Height" ?

Resources