Is there a way to control which items leave the visible area of a stackpanel when resizing?
Thanks
UPDATE 1
I have a fixed number of buttons inside a stackpanel. When resizing the visible area of the stackpanel, each button automatically hides or shows depending on the available space. What I'd like to achieve, through some event, control which of button hides or shows when stackpanel resizing occurs.
The reason is, I'd like to create a minimized version of the button instead of hiding the button.
In the past I have done something like this by altering the ContentTemplate of an object based on the object's size.
Typically I add an event to both the Loaded and SizeChanged events of the parent object, and from there figure out if the control is visible or not. If not, I change the template to a smaller version of the template.
In reference to your comment here about the SizeChanged event not firing, that is probably because you have your objects in a StackPanel, which will grow/shrink to fit the size of it's children, not to match the size of it's parent (the Grid cell).
You can probably also do this using a DataTrigger and Converter on the actual UI object, so it automatically checks to see if the Template should changed when the control's ActualWidth or ActualHeight changes.
I have a helper class I use to determine the exact visibility of a UI control within it's parent object, to find out if it's fully or partially visible, or hidden entirely. The code can be found in this answer, although I'll also copy it here:
public enum ControlVisibility
{
Hidden,
Partial,
Full,
FullHeightPartialWidth,
FullWidthPartialHeight
}
/// <summary>
/// Checks to see if an object is rendered visible within a parent container
/// </summary>
/// <param name="child">UI element of child object</param>
/// <param name="parent">UI Element of parent object</param>
/// <returns>ControlVisibility Enum</returns>
public static ControlVisibility IsObjectVisibleInContainer(
FrameworkElement child, UIElement parent)
{
GeneralTransform childTransform = child.TransformToAncestor(parent);
Rect childSize = childTransform.TransformBounds(
new Rect(new Point(0, 0), new Point(child.Width, child.Height)));
Rect result = Rect.Intersect(
new Rect(new Point(0, 0), parent.RenderSize), childSize);
if (result == Rect.Empty)
{
return ControlVisibility.Hidden;
}
if (result.Height == childSize.Height && result.Width == childSize.Width)
{
return ControlVisibility.Full;
}
if (result.Height == childSize.Height)
{
return ControlVisibility.FullHeightPartialWidth;
}
if (result.Width == childSize.Width)
{
return ControlVisibility.FullWidthPartialHeight;
}
return ControlVisibility.Partial;
}
You can get a control's visibility like this:
ControlVisibility ctrlVisibility =
WPFHelpers.IsObjectVisibleInContainer(button, parent);
if (ctrlVisibility == ControlVisibility.Full
|| isVisible == ControlVisibility.FullWidthPartialHeight)
{
// Set big template
}
else
{
// Set little template
}
The reason why the "SizeChanged" event wasn't firing was that I had set it's height to a fixed value. After setting it to "auto" the event fired when resizing it's parent container.
Related
I have a simple ListView in a Universal Windows App. It scrolls just fine both on a touch-enabled device and on my local machine. On the touch device I would like to be able to tap and drag the scroll bar thumb in order to quickly scroll through the list (same as you would get clicking and dragging the scroll thumb with a mouse). But, when I try to select the scroll bar thumb it does not work; the thumb is not selectable.
At first I thought the scroll thumb was just too small, so I fiddled with the default style of ScrollBar to increase the width. Then I tried tweaking other values in the default ScrollViewer style, like the ScrollingIndicatorMode and ScrollingIndicatorStates so that they would always use MouseIndicator, and making sure all IsHitTestVisible are True. To no avail.
It seems like there must be something in the default styles that allows this but I can't find it through trial and error, and no where in the MSDN docs appears to address this styling.
Is this doable in touch mode?
Is this doable in touch mode?
AFAIK, this is not possible currently. The scroll event of ScrollBar can only be triggered by scrolling the content or moving the Thumb by mouse input.
This is by design, but on the touch device if we want to quickly scroll through the list, we just quickly swipe one item for a short distance, the ListView will firstly speed up the scrolling and then slow down and finally stop. Other controls which contain a ScrollViewer behave the same, in the viewport(content of ScrollViewer) when in touch device, moving the thumb is not work, to speed up the scrolling, you can only give quick swipe gesture on the viewport.
Our suggestion is that you may submit a request to add this new features for developing through the Windows Feedback tool.
For posterity's sake I want to post my work around which actually turned out not being quite as difficult as I thought and looks really good now.
The basic idea is to use a Slider control (has a Thumb that is select-able, drag-able, and moves along a track) with a vertical orientation and to sync the Slider's Value property (which changes when dragged) to the ListView's ScrollIntoView method so you can scroll the ListView as the Slider Thumb is dragged. Vice-versa (move the Slider Thumb when the ListView scrolls normally) is also required for a clean, seamless experience. I have some code sample below to get you started (not everything is there to work right away). In addition, edit the Slider's default Style template to make it look exactly like a scroll bar (and even even add a tooltip to display the current value when dragging).
Note: I'm using WinRTXamlToolkit in code-behind to traverse the VisualTree easily.
Code behind:
public sealed partial class MainPage : Page
{
private bool _isScroll = false;
private bool _isSlide = false;
public MainPage()
{
this.InitializeComponent();
var vm = new ViewModel();
vm.ValueChanged += LetterSliderValueChanged;
DataContext = vm;
}
/// <summary>
/// Bring list items into view on the screen based on the value (letter) of the slider
/// </summary>
/// <param name="sender">The view model to bring into view</param>
private void LetterSliderValueChanged(object sender, RoutedEventArgs e)
{
if (_isScroll) return;
if (sender == null) return;
_isSlide = true;
ListView?.ScrollIntoView(sender, ScrollIntoViewAlignment.Leading);
}
/// <summary>
/// Update the position of the slider when the ListView is scrolling from a normal touch
/// </summary>
private void ScrollViewerViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
if (_isSlide)
{
_isSlide = false;
return;
}
_isScroll = true;
var scrollViewer = sender as ScrollViewer;
var scrollBars = scrollViewer.GetDescendantsOfType<ScrollBar>();
var verticalBar = scrollBars.FirstOrDefault(x => x.Orientation == Orientation.Vertical);
// Normalize the scales to move the slider thumb in sync with scrolling
var sliderTotal = LetterSlider.Maximum - LetterSlider.Minimum;
var barTotal = verticalBar.Maximum - verticalBar.Minimum;
var barPercent = verticalBar.Value / barTotal;
LetterSlider.Value = (barPercent * sliderTotal) + LetterSlider.Minimum;
_isScroll = false;
}
/// <summary>
/// Add the slider method to the ListView's ScrollViewer Viewchanged event
/// </summary>
private void ListViewLoaded(object sender, RoutedEventArgs e)
{
var listview = sender as ListView;
if (listview == null) return;
var scrollViewer = listview.GetFirstDescendantOfType<ScrollViewer>();
scrollViewer.ViewChanged -= ScrollViewerViewChanged;
scrollViewer.ViewChanged += ScrollViewerViewChanged;
}
}
XAML:
<Slider x:Name="LetterSlider" Orientation="Vertical"
Value="{Binding SliderValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Maximum="{Binding MaxSliderLetter, Mode=OneTime}"
Minimum="{Binding MinSliderLetter, Mode=OneTime}"
RenderTransformOrigin="0.5,0.5">
<Slider.RenderTransform>
<RotateTransform Angle="180"/>
</Slider.RenderTransform>
</Slider>
View Model:
//Method to update the ListView to show items based on the letter of the slider
public RoutedEventHandler ValueChanged;
public int SliderValue
{
get { return _sliderValue; }
set
{
_sliderValue = value;
NotifyPropertyChanged("SliderValue");
char letter = (char)_sliderValue;
var items = ItemsGroup.FirstOrDefault(i => (char)i.Key == letter);
if (items == null) return;
ValueChanged(items.FirstOrDefault(), new RoutedEventArgs());
}
}
I'm kinda new to this WPF world.
And I'm kinda confused on why my scrollview is consuming my touch event.
My current situation is this:
I have this ScrollViewer with an ItemsControl. In this ItemsControl I'm using a Wrappanel to show a number of Rectangles. My ScrollViewer should make it possible to scroll vertically to show the Rectangles that are wrapped downward.
On each of these Rectangles I made a CustomBehaviour with all kinds of handlers.
One of these handlers is a 'creatively' made way to handle LongPressGestures.
The problem is as follows, before my longpress is detected by the behaviour, my ScrollViewer is capturing my TouchDevice in its PreviewTouchMove handler.
How can I prevent my ScrollViewer from capturing my TouchDevice too early?
How can I make sure that I can scroll my ScrollViewer and do my LongPress, DoubleClick (which still works), SingleClick (which still works as well) and other gestures I might add to this custom behaviour?
I've found similar questions on stackoverflow and google that I just not figure out for my specific case.
Something with a CustomThumb <-- This link solves the problem by making a CustomThumb. Can I somehow re-use this for my behaviour? By capturing the TouchDevice early in my behaviour handlers?
If all things fail. Is there an alternative to this ScrollViewer & CustomBehaviour combination?
EDIT:
In the meantime. I retried the CustomThumb-method. I got the longpress to work now from my CustomBehaviour while the UIElements (with that behaviour) are located on a ScrollViewer.
However the scroll functionality of the ScrollViewer still does not work.
The bounty I added will also be awarded to the person that helps me get that to work properly again (since the answer should lie in the same direction as this CustomThumb solution).
I had a similar issue before. I could not use scrolling, touch and style in the same time. At the end I developped a custom ScrollViewer. It is easier than you think, since you only need to watch / change some basic methods.
In your case you can check whether the user pressed on an empy surface or a list item. If it is a list item, you need to check whether it was a short press (so, touchup also occured after touchdown) or a long one.
Scrolling can be configured with PanningMode. It allows you to scroll with finger all over the usercontrol.
Here is my version of scrollviewer. It turns scrolling mode off when user pressed a button and turned on afterwards.
public class ScrollViewerWithTouch : ScrollViewer
{
/// <summary>
/// Original panning mode.
/// </summary>
private PanningMode panningMode;
/// <summary>
/// Set panning mode only once.
/// </summary>
private bool panningModeSet;
/// <summary>
/// Initializes static members of the <see cref="ScrollViewerWithTouch"/> class.
/// </summary>
static ScrollViewerWithTouch()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ScrollViewerWithTouch), new FrameworkPropertyMetadata(typeof(ScrollViewerWithTouch)));
}
protected override void OnManipulationCompleted(ManipulationCompletedEventArgs e)
{
base.OnManipulationCompleted(e);
// set it back
this.PanningMode = this.panningMode;
}
protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
{
// figure out what has the user touched
var result = VisualTreeHelper.HitTest(this, e.ManipulationOrigin);
if (result != null && result.VisualHit != null)
{
var hasButtonParent = this.HasButtonParent(result.VisualHit);
// if user touched a button then turn off panning mode, let style bubble down, in other case let it scroll
this.PanningMode = hasButtonParent ? PanningMode.None : this.panningMode;
}
base.OnManipulationStarted(e);
}
protected override void OnTouchDown(TouchEventArgs e)
{
// store panning mode or set it back to it's original state. OnManipulationCompleted does not do it every time, so we need to set it once more.
if (this.panningModeSet == false)
{
this.panningMode = this.PanningMode;
this.panningModeSet = true;
}
else
{
this.PanningMode = this.panningMode;
}
base.OnTouchDown(e);
}
private bool HasButtonParent(DependencyObject obj)
{
var parent = VisualTreeHelper.GetParent(obj);
if ((parent != null) && (parent is ButtonBase) == false)
{
return HasButtonParent(parent);
}
return parent != null;
}
}
I have a dialog form with a mixture of design-time and run-time added controls. Each of these controls is hosted in a Panel. Each panel has FillStyle.Top set.
At run time I add a UserControl to a panel:
public NetworkDiscoveryDialog(CSNetworkDiscovery networkDiscovery) : this()
{
NetworkDiscovery = networkDiscovery;
SnmpCommunitiesUserControl = new SnmpCommunitiesUserControl(NetworkDiscovery.SnmpCommunitiesSetting);
panel2.Controls.Add(SnmpCommunitiesUserControl);
}
public partial class SnmpCommunitiesUserControl : UserControl
{
public SnmpSetting SnmpSetting { get; set; }
public SnmpCommunitiesUserControl()
{
InitializeComponent();
Anchor = (AnchorStyles.Top | AnchorStyles.Left);
Dock = DockStyle.Fill;
}
public SnmpCommunitiesUserControl(SnmpSetting snmpSetting)
: this()
{
SnmpSetting = snmpSetting;
}
}
Unfortunately, SnmpCommunitiesUserControl does not have the same position as the controls which were added at design-time.
I have tried:
Setting the Panel's margin as well as well as padding.
Setting the UserControl's margin as well as padding.
Anchor and Dock settings.
None of these seem to have any effect on the run-time added UserControl.
Previously, I was passing a 'Location' parameter to my UserControl's constructor. This allowed me to set the Location of the UserControl, but was not a maintainable solution.
How should I be going about doing this?
EDIT:
My current solution is to nest a a second panel inside of the first panel. I clear all margins and padding for both panels. The parent panel has Dock.Top with its child have Dock.Fill. Then, I set the child's Left/Right padding to 10. This causes the run-time added control to appear in the proper place. I'm not happy with this solution, though.
You should set Location and Size Properties by yourself or you can set Dock property of the new control. Another way is change your host panel to the StackLayoutPanel or TableLayoutPanel.
If you want to set absolute position, u should set the Location property to your position and set dock to none.
control.Location = new Point(x, y);
control.Dock = DockStyle.None;
I am using a ToolStripDropDown control to implement the dropdown portion of a custom ComboBox-like control. In order to be visually appealing, I am imposing a MaximumSize on the dropdown and manually specifying the width of each ToolStripButton within it - the result is a popup which is the same width as the control that activates it, with a cap on the height of the height of the dropdown portion.
Example (simplified):
ToolStripDropDown dropDown = new ToolStripDropDown();
dropDown.MaximumSize = new Size(200, 100);
dropDown.RenderMode = ToolStripRenderMode.System;
dropDown.AutoSize = true;
for (int i = 0; i < 50; i++) {
ToolStripButton dropDownItem = (ToolStripButton)dropDown.Items.Add("Item " + i);
dropDownItem.AutoSize = false;
dropDownItem.Size = new Size(200, 20);
}
dropDown.Show(owningControl, new Point(0, owningControl.Height - 1));
As you can see, the constraints on the popup's size are applied, however the up/down scroll buttons are not displayed and there seems to be no way to make them appear. There do not appear to be any methods or properties within ToolStripDropDown regarding the scrolling offset or a mechanism to scroll a particular item into view (such as EnsureVisible() on ListViewItem).
How, then, can I get the dropdown to scroll? Any method would be sufficient, be it a scroll bar, scroll buttons or even the mouse-wheel.
(Incidentally, I have tried many times to make similar controls using a Form for the dropdown portion - despite trying dozens of solutions to prevent the popup from stealing focus or gaining focus when its controls are clicked, this seems to be a dead end. I have also ruled out using ToolStripControlHost, whose hosted control can still take focus away from the form that opened it.)
Finally cracked this one. It occurred to me that ContextMenuStrip and ToolStripDropDownMenu are capable of the auto-scrolling behaviour which their base class, ToolStripDropDown, cannot provide. Initially, I avoided these alternative controls because they usually add a wide margin. This can be removed via ShowImageMargin and ShowCheckMargin. Even after doing this, a small (approx 5px) margin remains. This can be removed by overriding the DefaultPadding property:
public class MyDropDown : ToolStripDropDownMenu {
protected override Padding DefaultPadding {
get { return Padding.Empty; }
}
public MyDropDown() {
ShowImageMargin = ShowCheckMargin = false;
RenderMode = ToolStripRenderMode.System;
MaximumSize = new Size(200, 150);
}
}
// adding items and calling Show() remains the same as in the question
This results in a popup window which can contain any type of ToolStrip item, enforces MaximumSize, has no margin and, most importantly, does not steal focus and cannot receive focus.
This is your nemesis:
internal virtual bool RequiresScrollButtons
{
get
{
return false;
}
set
{
}
}
It is internal, you cannot override it. You can revive your approach of using a Form by fixing the focus stealing behavior. Paste this into the form class:
protected override bool ShowWithoutActivation
{
get { return true; }
}
I'm writing a custom TextBox that upon gaining focus changes its border style.
As adding a border causes the control to overlap with those neighbouring it, I temporarily bring the text box to the front of the dialog (using textBox.BringToFront()).
However, once editing is complete and focus is lost, I would like to send the control back to its original position in the Z-order.
Is this possible (preferably in a simple way!)
Call the GetChildIndex and SetChildIndex methods of the parent's Controls collection.
There is no Z-order as there was in VB, but you can use the GetChildIndex and SetChildIndex methods to get and set their indexes manually.
Here there's an example of how to use it. You will probably need to keep a record of each controls index though so you can set it back to it when it's finished with.
Something like this is probably what you're after:
// Get the controls index
int zIndex = parentControl.Controls.GetChildIndex(textBox);
// Bring it to the front
textBox.BringToFront();
// Do something...
// Then send it back again
parentControl.Controls.SetChildIndex(textBox, zIndex);
When used with the FlowLayoutPanel this will move a control up or down
/// <summary>
/// When used with the FlowLayoutPanel this will move a control up or down
/// </summary>
/// <param name="sender"></param>
/// <param name="UpDown"></param>
private void C_On_Move(object sender, int UpDown)
{
//If UpDown = 1 Move UP, If UpDown = 0 Move DOWN
Control c = (Control)sender;
// Get the controls index
int zIndex = _flowLayoutPanel1.Controls.GetChildIndex(c);
if (UpDown==1 && zIndex > 0)
{
// Move up one
_flowLayoutPanel1.Controls.SetChildIndex(c, zIndex - 1);
}
if (UpDown == 0 && zIndex < _flowLayoutPanel1.Controls.Count-1)
{
// Move down one
_flowLayoutPanel1.Controls.SetChildIndex(c, zIndex + 1);
}
}
In C Sharp
Control.SetValue(Panel.ZIndexProperty,0);
Control is your control.
0 is index of ZIndex.
0 is default value.