Is it possible to subscribe to a Property of a specific UIElement in WPF?
I want to animate an UIElement as soon as the Heightvalue changes and add the new height to a list, but I don't see how I can subscribe to the HeightProperty?
Samplecode:
Something like this:
MainWindow.xaml:
<Window x:Class="BibVisualization.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Background="Red" Width="30" Grid.Row="0" x:Name="myBorder">
<TextBlock Text="Really really long text with wrapping, but the wrapping changes based on border's width"
Width="{Binding ElementName=myBorder, Path=Width}"
TextWrapping="Wrap" />
</Border>
<Button Grid.Row="1" Height="10"
Content="Make border bigger" Click="OnButtonClick" />
</Grid>
</Window>
MainWindow.xaml.cs
private void OnButtonClick(Object sender, RoutedEventArgs e)
{
myBorder.Width += 10;
//Bind to textblock's actualheight and execute OnHeightChange?
}
private int accumulatedChange;
private void OnHeightChange(Object sender, SomeEventArgs? e)
{
accumulatedChange -= e.OldValue (if possible);
accumulatedChange += e.NewValue;
}
I think you can use the SizeChanged-Event of the FrameworkElement class to do what you want. All UIElements such as Button or Textblock derive from that class and therefore provide the event.
The SizeChangedEventArgs passed to registered method contains information if height or width has changed and provide the new values.
If I understood properly you'd want to 'bind' to ActualHeight ?
Take a look at this link (http://meleak.wordpress.com/2011/08/28/onewaytosource-binding-for-readonly-dependency-property/) - it describes how it can be done using attached properties basically.
Also take a look at this answer of mine from the other day, which basically describes very similar problem.
https://stackoverflow.com/a/15367642/417747
(use link there to download the support code - you can bind to via Style or as described in the article - it's all similar thing)
What you'd need is to bind to ActiveHeight using the method described in the article, that changes your view-model's MyHeight property - handle it's set to get when the active height changes. Let me know if any questions.
Hope it helps.
You could use the DependencyPropertyDescriptor to add a ValueChangedHandler for the property:
DependencyPropertyDescriptor descriptor=DependencyPropertyDescriptor.FromProperty(UIElement.HeightProperty,typeof(UIElement));
descriptor.AddValueChanged(myUIElementToWatch, new EventHandler(OnHeightOfUiElementChanged));
Related
So I'm working on a calculator, basically a copy of the Windows Version, as a training excercise. I have implemented a History of past calculations, and I was asked to transform this history from TextBox to Listview.
What I want to do is copy one of the past calculations back into the Calculator TextBox when I click on it, just like in the Windows Calculator.
My ListViewCode:
<ListView Grid.Column="0" Grid.Row="1" Foreground="#616161" Name="history" Background="Transparent"
HorizontalAlignment="Stretch" BorderThickness="0" Margin="10,10,10,0">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="MouseLeftButtonDown" Handler="RetrievePastCalculation" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
And this is the RetrievePastCalculation method, but it doesn't work, nothing happens when I click on a ListViewItem. I'm new to WPF by the way.
private void RetrievePastCalculation(object sender, MouseButtonEventArgs e)
{
innerTextBox.Text = history.SelectedItems.ToString();
}
This is where I add items to the ListView I think, it's the Equal button method:
private void ButtonEquals_Click(object sender, RoutedEventArgs e)
{
Calculator calculate = new Calculator();
textBox.Text = calculate.Calculate(innerTextBox.Text);
history.Items.Add(innerTextBox.Text + "=" + textBox.Text);
innerTextBox.Clear();
}
history.SelectedItems is a collection, so calling ToString on it won't give you anything other than the name of the type. If you try it in the debugger (which you should), you'll see that it returns System.Windows.Controls.SelectedItemCollection. Now, at this point you can either fix your issue one of two ways: you can continue to use your current event-based approach, or you can use binding.
Events
With events, you can hook a handler to the Selected event for each ListItem that you add to the list:
private void ButtonEquals_Click(object sender, RoutedEventArgs e)
{
Calculator calculate = new Calculator();
textBox.Text = calculate.Calculate(innerTextBox.Text);
var item = new ListViewItem();
item.Content = innerTextBox.Text + "=" + textBox.Text;
item.Selected += HistoryItem_Selected //hooks the handler to the 'Selected' event
history.Items.Add(item);
innerTextBox.Clear();
}
then define the handler itself:
private void HistoryItem_Selected(object sender, RoutedEventArgs e)
{
// here 'sender' will be the ListItem which you clicked on
// but since it's an object we need to cast it first
ListViewItem listItem = (ListViewItem)sender;
// now all that's left is getting the text and assigning it to the textbox
innerTextBox.Text = listItem.Content.ToString();
}
Binding
Binding is much simpler as far as the amount of code is concerned, but has a steeper learning curve. Here, instead of setting the TextBox.Text property directly, we will specify a binding expression. This means that the value will always be the same as that of the bound expression.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<ListView Grid.Column="0" Grid.Row="0" Name="history" />
<TextBox Text="{Binding ElementName=history, Path=SelectedItem.Content}" />
<Button Name="ButtonEquals" Content="equals" Click="ButtonEquals_Click"/>
</StackPanel>
</Grid>
</Window>
I've run this in a new WPF project and it works as expected: the text box displays whatever text is in the clicked item from the list.
One thing to note is that both solutions assume that you are assigning strings to the ListViewItem Content. As you may know, you can assign other controls or any object to the Content property of a UI Control (ListViewItem inherits from Control). That's why the ListViewItem.Add method takes an argument of type object and is not restricted to one of type string. If you assigned anything other than a string in your button click event handler, both of the two cases above would likely break.
You could bind the value of the TextBox to the SelectedItem of the ListView. Here's an example:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<ListView Grid.Column="0" Grid.Row="0" Foreground="#616161" Name="history" Background="Transparent"
HorizontalAlignment="Stretch" BorderThickness="0" Margin="10,10,10,0">
<ListViewItem>Calc1</ListViewItem>
<ListViewItem>Calc2</ListViewItem>
</ListView>
<TextBox Text="{Binding ElementName=history, Path=SelectedItem.Content}" />
</StackPanel>
</Page>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition Height="100"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListView Grid.Column="0" Grid.Row="0" Foreground="#616161" Name="history" BorderThickness="1,1" Height="50" Width="200" SelectionChanged="history_SelectionChanged">
<ListViewItem>
<TextBlock> A ListView</TextBlock>
</ListViewItem>
<ListViewItem>
with several
</ListViewItem>
<ListViewItem>
items
</ListViewItem>
</ListView>
<TextBox Grid.Row="1" Text="{Binding ElementName=history,Path=SelectedValue.Content}"
BorderThickness="1,1" Height="50" Width="200" />
</Grid>
It's better if you do it using XAML code. try to select item 0 and 1 to see the difference and understand how listboxworks.
now replace the text of textbox binding with following:
Text="{Binding ElementName=history,Path=SelectedValue.Content.Text}"
and seee the output for item 0. Hopefully you'll achieve desired output with a lot less effort.
Now that you have explained the whole problem i think you need to implement a converter in the text binding of TextBox. like below text
Text="{Binding ElementName=history,Path=SelectedValue.Content.Text,Converter={StaticResource mytextconverter}}"
and write down a logic to extract a part of text on the basis of '=' char. It's very easy to write a converter class. to write a converter follow the below link:
WPF Converter example
I am new for WPF so apologies if the answer is so obvious.
I got a WPF resizable window with a single stack panel control that is stretched vertically and horizontally to fill the window.
On window activated event, I use ".Children.Add" to add button controls to the panel. I have no idea how many buttons will be there at runtime so I checked "CanVerticallyScroll" in the panel. ScrollViewer.VerticalScrollBarVisibility is set to Visible by default.
I am still not seeing scroll bars at runtime though.
What properties did I miss to show scrolling panel with buttons?
Thanks
XAML:
<Window x:Class="ResMed.Ecp.Utility.ConnectionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ConnectionWindow" Height="388" Width="641.6" Activated="Window_Activated">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="359*"/>
</Grid.RowDefinitions>
<StackPanel x:Name="pnlConnectionButtons" Margin="10,10.2,10.2,10" Grid.Row="1" CanVerticallyScroll="True"/>
</Grid>
</Window>
Code behind:
private void Window_Activated(object sender, EventArgs e)
{
for (int i = 0; i < 20; i++)
{
Button btn = new Button();
btn.Content = "Hello";
pnlConnectionButtons.Children.Add(btn);
}
}
Place your StackPanel inside a ScrollViewer:
<ScrollViewer>
<StackPanel>
<Button Content="Hello World"></Button>
...
...
</StackPanel>
</ScrollViewer>
You can also remove CanVerticallyScroll="True". From MSDN:
This property is not intended for use in your code. It is exposed publicly to fulfill an interface contract (IScrollInfo). Setting this property has no effect.
This is my XAML
<Grid.Resources>
<SolidColorBrush x:Key="DynamicBG"/>
</Grid.Resources>
<Label name="MyLabel" Content="Hello" Background="{DynamicResource DynamicBG} />
So I have two questions:
Q1: How do I go about setting the DynamicBG key value to Red in my code now? (When the window loads, I'd like to set it to red)
Q2: Is this how dynamic resources are supposed to be used?
Thank you
To gain access to the Resource of the code must identify them in the file App.xaml:
<Application.Resources>
<SolidColorBrush x:Key="DynamicBG" />
</Application.Resources>
XAML example
<Grid>
<Label Name="MyLabel"
Content="Hello"
Background="{DynamicResource DynamicBG}" />
<Button Content="Change color"
Width="100"
Height="30"
Click="Button_Click" />
</Grid>
The Resource can be changed in code line of the form:
Application.Current.Resources["MyResource"] = MyNewValue;
Example:
Code behind
// using ContentRendered event
private void Window_ContentRendered(object sender, EventArgs e)
{
SolidColorBrush MyBrush = Brushes.Aquamarine;
// Set the value
Application.Current.Resources["DynamicBG"] = MyBrush;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SolidColorBrush MyBrush = Brushes.CadetBlue;
// Set the value
Application.Current.Resources["DynamicBG"] = MyBrush;
}
Principle, DynamicResources were designed, so they can be changed. Where to change - it is the task of the developer. In the case of Color, it is one of the most common methods. See the MSDN, for more information.
P. S. I recommend using App.xaml, because there have been cases where a StaticResource has been used successfully, but not DynamicResource (resources are placed in the Window.Resources). But after moving the resource in App.xaml, everything started to work.
A1:
You should move "DynamicBG" to window resource and after that you can use Resources property in Loaded event handler:
XAML:
<Window x:Class="MyLabelDynamicResource.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded">
<Window.Resources>
<SolidColorBrush x:Key="DynamicBG"/>
</Window.Resources>
<Grid>
<Label Name="MyLabel" Content="Hello" Background="{DynamicResource DynamicBG}" />
</Grid>
</Window>
Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.Resources["DynamicBG"] = new SolidColorBrush(Colors.Red);
}
}
A2:
You should use dynamic resources when you want to change property in runtime.
A2: no. To do what you are doing, it is better to use data binding. Have a property in your viewmodel indicating whether it's 'loaded', then bind the background to it with a suitable converter, or use a trigger. (If it's actually UI that is loading, add the property to the window.) Dynamic resources are used for theming and with templates, in the rare cases when a StaticResource lookup happens too early.
I need to add several user controls to a Canvas. The size of the UserControl depends on the number of items present in the ItemsControl of the UserControl. To position the controls properly and to draw interconnecting lines between the usercontrols, I need the absolute width/height w.r.t the parent canvas. How to get these? ActualHeight and ActualWidth are returning 0.
I had asked similar question earlier, but could not get the right answer.
EDIT: Adding XAML od UserControl
<UserControl x:Class="SilverlightApplication2.MyControl"
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"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}" Loaded="UserControl_Loaded">
<Grid x:Name="LayoutRoot" Background="White">
<Border CornerRadius="3" BorderThickness="1" BorderBrush="LightGray">
<Grid Name="grid1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<Grid.RowDefinitions>
<RowDefinition Height="40*" />
<RowDefinition Height="136*" />
</Grid.RowDefinitions>
...
<Grid Name="gridPC" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="55*" />
<RowDefinition Height="*" />
<RowDefinition Height="55*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
....
<ItemsControl x:Name="pitems" ItemsSource="{Binding RowsP}" Grid.Row="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Width="250" Orientation="Horizontal">
<TextBlock Text="{Binding X}" Width="100" />
<TextBlock Text="{Binding Y}" Width="130" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
......
</Grid>
</Grid>
</Border>
</Grid>
You have few options you can do this, forcing to call Window.Measure and Window.Arrange will make all values to be calculated, or you can get those values in the Window.Loaded event. This same issue is discussed already on this question.
If you are sizing to content:
window.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
window.Arrange(new Rect(0, 0, window.DesiredWidth, window.DesiredHeight));
If you are using an explicit window size:
window.Measure(new Size(Width, Height));
window.Arrange(new Rect(0, 0, window.DesiredWidth, window.DesiredHeight));
or
public MyWindow()
{
Loaded += delegate
{
// access ActualWidth and ActualHeight here
};
}
ActualWidth and ActualHeight work only after the control has been rendered.
To get the desired size of a control, you need to let it measure itself by calling it's Measure method. After that, you can use DesiredSize property which will contain the values you seek.
There is also a good article by Charles Petzold handling a similar situation:
Thinking Outside the Grid
I am actively creating a Silverlight timeline control and I don't do a measure as the previous posters advised. I simply wait for the final onsize call or other dependent properties OnChanged event.
Here is what I do to load my control which has a canvas:
Subscribe to the user control's Loaded event (which is the target for all dependent load events calls and on size event in (step #2)).
Subscribe to the Size Changed event (which calls the controls loaded event as mentioned in #1).
All dependent properties OnXXXPropertyChanged event call the OnLoad (#1).
Within the loaded event I check for this.ActualWidth to be set (non zero) along with whether all of my dependent properties are valid. (If they are all not set and also my boolean global flag states that it hasn't been loaded yet; it does nothing and exits (waiting a subsequent call).
Once my load event detects all dependent properties have been set and ActualWidth is not zero, it then begins the process of using the width and the dependent properties to begin to create my sub controls.
HTH
It has been a long time but I did something similar a few years ago. I don't recall all the details right now, I will look at this when I have more time this evening. I wanted to give you a quick idea of why you are getting 0 for the size.
This is primarily because the layout system in WPF occurs in two passes, the measure and the arrange pass. First the container control (in your case the Panel) asks it's children for their size once this pass is completed, the container will arrange it children using the sizes they calculated in the measure pass.
I would recommend reading the this MSDN article focusing on the Measuring and Arranging Children section. http://msdn.microsoft.com/en-us/library/ms745058.aspx
Let me know if this does not help, and I will spend some more time to refresh my memory on all the details.
Based on your edit you may want to check out this post describing how to create objects that are connected with lines. http://denisvuyka.wordpress.com/2007/10/21/wpf-diagramming-drawing-a-connection-line-between-two-elements-with-mouse/
I think this is more directed at your exact scenario.
I have a rectangle in my XAML and want to change its Canvas.Left property in code behind:
<UserControl x:Class="Second90.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300" KeyDown="txt_KeyDown">
<Canvas>
<Rectangle
Name="theObject"
Canvas.Top="20"
Canvas.Left="20"
Width="10"
Height="10"
Fill="Gray"/>
</Canvas>
</UserControl>
But this doesn't work:
private void txt_KeyDown(object sender, KeyEventArgs e)
{
theObject.Canvas.Left = 50;
}
Does anyone know what the syntax is to do this?
Canvas.SetLeft(theObject, 50)
Try this
theObject.SetValue(Canvas.LeftProperty, 50d);
There is a group of methods on DependencyObject (base of most WPF classes) which allow the common access to all dependency properties. They are
SetValue
GetValue
ClearValue
Edit Updated the set to use a double literal since the target type is a double.
As we are changing the property of the 'object', it would be better to use method suggedte by JaredPar:
theObject.SetValue(Canvas.LeftProperty, 50d);