WPF UI AddIn not updating its desired size - c#

I have an application that gets one of its UI controls via an INativeHandleContract from a different AppDomain. When the size of the control changes the FrameworkElement in the host doesn't get an updated size. I was wondering if there was any way to fix this.
Here is the sample code (the XAML is just a blank window):
public partial class Window1 : Window
{
private StackPanel m_stackPanel;
private Expander m_expander;
private UIElement m_expanderAddIn;
public Window1()
{
InitializeComponent();
m_stackPanel = new StackPanel { Orientation = Orientation.Vertical };
m_stackPanel.Background = Brushes.Red;
m_expander = new Expander
{
ExpandDirection = ExpandDirection.Right,
Background=Brushes.Blue,
IsExpanded=true,
};
m_expander.Expanded += CheckStuff;
m_expander.Collapsed += CheckStuff;
Rectangle r = new Rectangle {Fill = Brushes.LightGray, Height = 300, Width = 300};
m_expander.Content = r;
m_expanderAddIn = FrameworkElementAdapters.ContractToViewAdapter(FrameworkElementAdapters.ViewToContractAdapter(m_expander));
m_stackPanel.Children.Add(m_expanderAddIn);
Content = m_stackPanel;
}
private void CheckStuff(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Expander: " + m_expander.DesiredSize);
Debug.WriteLine("Add in: " + m_expanderAddIn.DesiredSize);
Debug.WriteLine("Stack Panel: " + m_stackPanel.DesiredSize);
}
}
As you expand and collapse the Expander you would expect the height of the StackPanel to change but it doesn't. Any ideas would be useful, thanks.

That's really weird and I have no idea why it behaves like that. This is probably just a small example reproducing your problem so maybe this doesn't help you much but I got your example working with 3 changes.
Wrap the Expander in a "dummy" StackPanel and use that as the root element for m_expanderAddIn instead. This took care of the Height problem for the Expander.
Change the type of m_expanderAddIn from UIElement to FrameworkElement
Bind the Height of m_expanderAddIn to ActualHeight of m_expander
Code
public partial class Window1 : Window
{
private StackPanel m_stackPanel;
private Expander m_expander;
private FrameworkElement m_expanderAddIn;
public Window1()
{
InitializeComponent();
m_stackPanel = new StackPanel { Orientation = Orientation.Vertical };
m_stackPanel.Background = Brushes.Red;
m_expander = new Expander
{
ExpandDirection = ExpandDirection.Right,
Background=Brushes.Blue,
IsExpanded=true,
};
m_expander.Expanded += CheckStuff;
m_expander.Collapsed += CheckStuff;
Rectangle r = new Rectangle {Fill = Brushes.LightGray, Height = 300, Width = 300};
m_expander.Content = r;
StackPanel stackPanel = new StackPanel();
stackPanel.Children.Add(m_expander);
m_expanderAddIn = FrameworkElementAdapters.ContractToViewAdapter(FrameworkElementAdapters.ViewToContractAdapter(stackPanel));
Binding binding = new Binding("ActualHeight");
binding.Source = m_expander;
m_expanderAddIn.SetBinding(FrameworkElement.HeightProperty, binding);
m_stackPanel.Children.Add(m_expanderAddIn);
Content = m_stackPanel;
}
private void CheckStuff(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Expander: " + m_expander.DesiredSize);
Debug.WriteLine("Add in: " + m_expanderAddIn.DesiredSize);
Debug.WriteLine("Stack Panel: " + m_stackPanel.DesiredSize);
}
}

Related

How do I get the height and width from a DockPanel?

I am using a DockPanel to host some image enhancement controls. The image to be enhanced is in the 'center' dock position. I would like to have the image fill the entire area of the DockPanel. I have a custom class that implements the WPF Canvas control and takes a windows.Rect as input. This Rect will set the area for the image render function which I have overridden.
I have tried to get the DockPanel.ActualHeight and .ActualWidth. both are NaN and the DockPanel.Width and Height = 0.
public MainWindow()
{
ImageDataModel = new ImageDataModel();
DataContext = ImageDataModel;
InitializeComponent();
WindowState = WindowState.Maximized;
var tmpWindow = Window.GetWindow(BaseDockPanel);
Rect rect = new Rect(0, 0, BaseDockPanel.ActualWidth, BaseDockPanel.ActualHeight);
bitmapCanvas = new BitMapCanvas(rect);
BaseDockPanel.Children.Add(bitmapCanvas);
}
I would like to be able to create a windows.Rect that is the actual dimension of the DockPanel.center height and width. Currently the height and width for the control are 0.
At the point of the construction of MainWindow, the child controls within the Window, in your case BaseDockPanel has not been loaded yet.
That is why you're getting a 0 for it's ActualWidth and ActualHeight.
What you can do is add an EventHandler for the Window's Loaded event as shown below.
public MainWindow()
{
ImageDataModel = new ImageDataModel();
DataContext = ImageDataModel;
InitializeComponent();
WindowState = WindowState.Maximized;
this.Loaded += Window_Loaded;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var tmpWindow = Window.GetWindow(BaseDockPanel);
// You will get your ActualWidth and ActualHeight here.
Rect rect = new Rect(0, 0, BaseDockPanel.ActualWidth, BaseDockPanel.ActualHeight);
bitmapCanvas = new BitMapCanvas(rect);
BaseDockPanel.Children.Add(bitmapCanvas);
}

UWP Layout Cycle inside StackPanel when resizing children

I have a StackPanel with 2 children that I need to resize based on the StackPanels size. I attached an event handler to SizeChanged of the StackPanel and resize the element there. The problem is the event handler is fired again and again until the app crashes. What's the deal?
class BasicStatusTile : StackPanel {
private TextBlock TopText { get; set; }
private TextBlock BottomText { get; set; }
public BasicStatusTile() {
InitializeComponent();
}
private void InitializeComponent() {
TopText = new TextBlock() {
Text = "0",
HorizontalAlignment = Windows.UI.Xaml.HorizontalAlignment.Stretch,
TextAlignment = Windows.UI.Xaml.TextAlignment.Center,
FontSize = 48
};
BottomText = new TextBlock() {
Text = "Speed (km/h)",
HorizontalAlignment = Windows.UI.Xaml.HorizontalAlignment.Stretch,
VerticalAlignment = Windows.UI.Xaml.VerticalAlignment.Bottom,
TextAlignment = Windows.UI.Xaml.TextAlignment.Center
};
base.Children.Insert(0, TopText);
base.Children.Insert(1, BottomText);
base.Background = new SolidColorBrush(Colors.White);
base.SizeChanged += BasicStatusTile_SizeChanged;
}
private void BasicStatusTile_SizeChanged(object sender, Windows.UI.Xaml.SizeChangedEventArgs e) {
double third = base.ActualHeight / 6;
TopText.FontSize = third * 4;
BottomText.FontSize = third;
}
}
This happened because you change FontSize of TextBlock. It fires the sizechanged event of your custom StackPanel.
You should consider what kind of control you need.
What is your purpose of using custom stackpanel with sizechanged event?
Also, FontSize is not the same as a Height of TextBlock

c# wpf - ListView.ScrollIntoView(LastItem) does not work properly

like some other people here i have a ListView (updated via binding in a GridView).
I want to keep the last inserted Item in the View. So i tried
LView.ScrollIntoView(LView.Items[LView.Items.Count - 1]);
This is working almost fine. Altough the first item which would have to be scrolled into view is only shown like 80% of its whole row (depending on how high i define the whole ListView, i almost got 100%).
The real problem is that the following items which should get scrolled into view are not shown. It is also noticable at the Scrollbar itself which is not at the bottom.
Last Item is not shown
Here is the code of my MainWindow.
public partial class MainWindow : Window
{
private InterfaceCtrl ICtrl;
private ListView LView;
public MainWindow()
{
InitializeComponent();
this.ICtrl = new InterfaceCtrl();
this.ICtrl.ProgressCollection.CollectionChanged += this.CollectionChanged;
Grid MainGrid = new Grid();
this.Content = MainGrid;
GridView gv = new GridView();
Binding StartTimeStampBinding = new Binding() { Path = new PropertyPath("StartTS"), Mode = BindingMode.OneWay, StringFormat = "dd.MM.yyyy - HH:mm:ss.fff" };
GridViewColumn gvTCStartTS = new GridViewColumn() { Header = "Time", Width = 150.00, DisplayMemberBinding = StartTimeStampBinding };
gv.Columns.Add(gvTCStartTS);
LView = new ListView() { Height = 192, Width = 250, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, View = gv, ItemsSource = this.ICtrl.ProgressCollection };
MainGrid.Children.Add(LView);
ICtrl.StartMyThread();
}
private void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Application.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new System.Action(delegate ()
{
if (LView != null && LView.Items.Count > 0)
{
LView.UpdateLayout();
//LView.ScrollIntoView(LView.Items[LView.Items.Count - 1]);
LView.SelectedIndex = LView.Items.Count;
LView.ScrollIntoView(LView.SelectedItem);
}
}));
}
}
Thank you.
EDIT:
It seemed to be a timing problem, although all the wanted data was in the LView at the right time i tried a workaround with a Textbox bound to the Timestamp.
TextBox tb = new TextBox(); // { Width = 250, Height = 28, Margin= new Thickness(10,100,1,0)};
tb.SetBinding( TextBox.TextProperty , new Binding("LastMsgTimestamp") { Source = this.ICtrl, Mode = BindingMode.OneWay, StringFormat = "dd.MM.yyyy - HH:mm:ss.fff" });
tb.TextChanged += this.UpdateScrollbar;
tb.Visibility = Visibility.Hidden;
It seems to me like there is a timing issue within the binding to the LView and the fired Event of the ObservableCollection. This also includes the PropertyChanged of the ObservableCollection.
I tried the events TargetUpdated and SoruceUpdated directly within LView but those didn't came up at all.
You could try to call any of the ScrollToBottom() or ScrollToVerticalOffset() methods of the ListView's internal ScrollViewer element:
private void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Application.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new System.Action(delegate ()
{
if (LView != null && LView.Items.Count > 0)
{
LView.UpdateLayout();
ScrollViewer sv = GetChildOfType<ScrollViewer>(LView);
if (sv != null)
sv.ScrollToBottom();
LView.SelectedIndex = LView.Items.Count;
LView.ScrollIntoView(LView.SelectedItem);
}
}));
}
private static T GetChildOfType<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj == null)
return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null)
return result;
}
return null;
}
I have made the following sample. You could try to call ScrollToBottom in inner ScrollViewer as #mm8 points out. Nevertheless when saw the answer I was already making my sample, so here it is:
Codebehind
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ListViewScroll
{
public partial class MainWindow : Window
{
public ObservableCollection<string> Names { get; set; }
public MainWindow()
{
InitializeComponent();
Names = new ObservableCollection<string>();
ListView.ItemsSource = Names;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Names.Add("Some Name" + ++someInt);
// Get the border of the listview (first child of a listview)
var border = VisualTreeHelper.GetChild(ListView, 0) as Decorator;
// Get scrollviewer
var scrollViewer = border.Child as ScrollViewer;
scrollViewer.ScrollToBottom();
}
private static int someInt;
}
}
XAML
<Window x:Class="ListViewScroll.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>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListView Grid.Row="0" Name="ListView"/>
<Button Content="Add" FontSize="20" Grid.Row="1"
Click="ButtonBase_OnClick"/>
</Grid>
</Window>
In this case I am handling the scrolling in the button click event but you may change this to fit your requirements
It works, I have tested.
Hope this helps

Transparent canvas, with opaque elements

I'm trying to simulate an Android UI element that unfortunately doesn't exist in Windows 7 phone: ListPreference
I thought about using a Popup, that would take exactly the whole screen (to simulate a modal window).
So the popup would be made of the following elements:
Popup -> Canvas -> Border -> StackPanel -> RadioButtons
The Canvas would be fully transparent (or lightly whitish to clearly show that the element underneath aren't available)
The border would be made so it only big enough to contain all the RadioButtons
Then the StackPanel would be opaque and black.
Unfortunately, if I make the bottom canvas transparent, all children elements are also transparent. I can only make the elements more transparent.
The way transparency works is slightly different than with Android or iPhone (where it's quite easy to have a parent fully transparent, but opaque children).
Is there a way to make a parent fully transparent with the children opaque?
Or maybe someone could suggest another way to simulate a modal window.
Who knows, maybe someone even developed a ListPreference-like UIElement :)
Thank you
Here is how I ended up doing it.
It works in a similar fashion as ListPreference on Android. The constructor takes a string, an array of string and an int indicating which is the default value
When the windows is closed, the delegate Dismissed is called..
So you call it like so:
string[] choices = { "Choice 1", "Choice 2", "Choice3" };
ListPreference lp = new ListPreference("name", choices, 1);
lp.dismissed += new ListPreferences.DismissedHandler(lp_Dismissed);
the code:
public class ListPreference
{
Popup p;
string Name;
int oldValue;
public delegate void DismissedHandler(string name, bool changed, int newvalue);
public event DismissedHandler Dismissed;
public bool IsOpen
{
get
{
return p.IsOpen;
}
set
{
p.IsOpen = value;
}
}
public ListPreference(string name, Array elements, int default_value)
{
p = new Popup();
Name = name;
Dismissed = null;
oldValue = default_value;
double height = (App.Current.RootVisual as FrameworkElement).ActualHeight;
double width = (App.Current.RootVisual as FrameworkElement).ActualWidth;
p.VerticalOffset = SystemTray.IsVisible ? 32.0 : 0.0;
p.Height = height;
p.Width = width;
Canvas canvas = new Canvas();
SolidColorBrush colorBrush = new SolidColorBrush(Colors.Black);
colorBrush.Opacity = 0.75;
//Color.FromArgb(0xff, 0x8a, 0x8a, 0x8a));
canvas.Background = colorBrush;
//canvas.Opacity = 0.765;
canvas.Height = height;
canvas.Width = width;
p.Child = canvas;
Border border = new Border();
border.Width = width - 50.0 * 2.0;
border.BorderBrush = new SolidColorBrush(Colors.LightGray);
border.BorderThickness = new Thickness(5.0);
border.Background = new SolidColorBrush(Colors.Black);
canvas.Children.Add(border);
StackPanel panel2 = new StackPanel();
panel2.Orientation = System.Windows.Controls.Orientation.Vertical;
int i = 0;
foreach (string val in elements)
{
RadioButton radio1 = new RadioButton();
radio1.GroupName = "group1";
radio1.Content = val;
if (i == default_value)
radio1.IsChecked = true;
int j = i;
radio1.Click += (sender, args) => radio1_Checked(radio1, j);
i++;
panel2.Children.Add(radio1);
}
Button button1 = new Button();
button1.Background = new SolidColorBrush(Colors.Black);
button1.Foreground = new SolidColorBrush(Colors.White);
button1.Opacity = 1.0;
button1.Content = "Cancel";
button1.Margin = new Thickness(5.0);
button1.Click += new RoutedEventHandler(closeButton_Click);
panel2.Children.Add(button1);
border.Child = panel2;
// Open the popup.
p.IsOpen = true;
p.UpdateLayout();
border.Height = panel2.DesiredSize.Height + 5.0 * 2.0;
border.SetValue(Canvas.TopProperty, (height - border.Height) / 2.0);
border.SetValue(Canvas.LeftProperty, (width - border.Width) / 2.0);
p.UpdateLayout();
}
void closeButton_Click(object sender, RoutedEventArgs e)
{
// Close the popup.
p.IsOpen = false;
if (Dismissed != null)
{
Dismissed(Name, false, -1);
}
}
void radio1_Checked(object sender, int idx)
{
p.IsOpen = false;
if (Dismissed != null)
{
Dismissed(Name, idx != oldValue, idx);
}
}
}
I would suggest creating a Usercontrol that would do what you need. Set the LayoutRoot grid's background to PhoneSemitransparentBrush or changing the opacity will change the child element's opacity as well. Then your child elements can have any opacity you'd like. You can add this control as a child to the popup. Additionally, you can add doubleanimation to the popup with the opened and closed event triggers. Change the design height of the UserControl to 480x760 to simulate full page.
To answer your question. Using resources like PhoneSemitransparentBrush and TransparentBrush for the Canvas background is one of your options. Opacity will change the opacity of the whole UIElement including its children.

Position of dragged&dropped element (MouseDragElementBehavior)

I'm trying to implement a WPF application with drag&drop functionality using MouseDragElementBehavior.
But I just can't find a way to get the dropped elements position relative to its parent Canavs.
Example code:
namespace DragTest {
public partial class MainWindow : Window {
private Canvas _child;
public MainWindow() {
InitializeComponent();
Canvas parent = new Canvas();
parent.Width = 400;
parent.Height = 300;
parent.Background = new SolidColorBrush(Colors.LightGray);
_child = new Canvas();
_child.Width = 50;
_child.Height = 50;
_child.Background = new SolidColorBrush(Colors.Black);
MouseDragElementBehavior dragBehavior = new MouseDragElementBehavior();
dragBehavior.Attach(_child);
dragBehavior.DragBegun += onDragBegun;
dragBehavior.DragFinished += onDragFinished;
Canvas.SetLeft(_child, 0);
Canvas.SetTop(_child, 0);
parent.Children.Add(_child);
Content = parent;
}
private void onDragBegun(object sender, MouseEventArgs args) {
Debug.WriteLine(Canvas.GetLeft(_child));
}
private void onDragFinished(object sender, MouseEventArgs args) {
Debug.WriteLine(Canvas.GetLeft(_child));
}
}
}
After Dropping the child Canvas the value of Canvas.GetLeft(_child) is still 0.
Why that? Why doesn't it change?
Sure, I can get the new position by using dragBehavior.X, but that's the child Canvas' position in the main window, not the position relative to the parent Canvas. There must be a way to get it...
I just found a workaround:
private void onDragFinished(object sender, MouseEventArgs args) {
Point windowCoordinates = new Point(((MouseDragElementBehavior)sender).X, ((MouseDragElementBehavior)sender).Y);
Point screenCoordinates = this.PointToScreen(windowCoordinates);
Point parentCoordinates = _parent.PointFromScreen(screenCoordinates);
Debug.WriteLine(parentCoordinates);
}
So I simple convert the point to screen coordinates, then from screen coordinates to the parents coordinates.
Nevertheless there will be problems if the parent Canvas is in some ScrollView or somthing.
There doesn't seem to be a simple solution with this Drag&Drop approach...

Categories