Same with the subject, When i add some item in ListBox, also Listbox's Scroll Bar is moved automatically.
Because verticalOffset is same, but ExtraHeight is enlarged.
I Just want Don't move scrollBar when i insert some item...
I use both of ObservableCollection's Insert(0, Object) method and Add() method
The Symptom appears when VerticalOffSet is not 0 nor max Height.
You can see that like below
→
And i already moved it manually(with Code)
But when i move it original position, I should watch the move animation.
Did you have an idea?
Plz know me about that.
In WPF the ItemsControl maintains the scroll offset relative to the beginning of the list, forcing items in the viewport to move down when items are added to the ItemsSource. UWP allows to customize this behavior e.g. to behave the way you need it.
I recommend to extend ListBox or alternatively create an attached behavior.
The following example extends ListBox and handles the internal ScrollViewer to adjust the scroll offset to keep the first visible item in the viewport when items are added to the ItemsSource:
class KeepItemsInViewListBox : ListBox
{
private ScrollViewer ScrollViewer { get; set; }
#region Overrides of FrameworkElement
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (TryFindVisualChildElement(this, out ScrollViewer scrollViewer))
{
this.ScrollViewer = scrollViewer;
}
}
#endregion
#region Overrides of ListView
/// <inheritdoc />
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (this.ScrollViewer == null)
{
return;
}
double verticalOffset;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add when e.NewItems != null:
// Check if insert or add
verticalOffset = e.NewStartingIndex < this.ScrollViewer.VerticalOffset
? this.ScrollViewer.VerticalOffset + e.NewItems.Count
: this.ScrollViewer.VerticalOffset;
break;
case NotifyCollectionChangedAction.Remove when e.OldItems != null:
verticalOffset = this.ScrollViewer.VerticalOffset - e.OldItems.Count;
break;
default:
verticalOffset = this.ScrollViewer.VerticalOffset;
break;
}
this.ScrollViewer?.ScrollToVerticalOffset(verticalOffset);
}
#endregion
public bool TryFindVisualChildElement<TChild>(DependencyObject parent, out TChild childElement)
where TChild : FrameworkElement
{
childElement = null;
if (parent == null)
{
return false;
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if (child is TChild resultElement)
{
childElement = resultElement;
return true;
}
if (TryFindVisualChildElement(child, out childElement))
{
return true;
}
}
return false;
}
}
The class can be enhanced by making the behavior optional e.g., by introducing an enum.
I'm trying to display a list view with cells that are rendered with a custom iOS renderer. When scrolling the list, there are nasty overlays (as shown below). I boiled it down to the following lines of code.
The App contains a ListView with its ItemsSource set to a dummy list of all characters from A to Z. The ItemTemplate is created with a custom ItemCell defined below.
public class App : Application
{
public App()
{
MainPage = new ContentPage {
Content = new ListView {
ItemsSource = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray(),
ItemTemplate = new DataTemplate(typeof(ItemCell)),
},
};
}
}
The ItemCell contains a Label bound to the list item.
public class ItemCell: ViewCell
{
public ItemCell()
{
View = new Label();
View.SetBinding(Label.TextProperty, ".");
}
}
On iOS the Label is rendered with the following FixedLabelRenderer. It sets a native control represented by a UILabel containing the Element's Text.
public class FixedLabelRenderer : ViewRenderer<Label, UILabel>
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
SetNativeControl(new UILabel(RectangleF.Empty) { Text = Element.Text });
}
}
Now the problem is that, apparently, when reusing a cell, the control is not removed, but only a new one is created. So I'm getting the overlays shown in the screenshots below.
(Left: before scrolling, Right: after scrolling)
Interestingly this issue doesn't happen with Xamarin.Forms 1.2.3 or 1.3.1, but with 1.3.5 and 1.4.0.
Oh, I found a solution on the Xamarin Forum:
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control == null)
SetNativeControl(new UILabel());
if (e.NewElement != null)
Control.Text = e.NewElement.Text;
}
So basically you need to make sure to only create one UILabel and reuse it afterwards.
I would just simply like to be able to minimize the application bar when I am scrolling down, and then show its normal size when scrolling up. I've seen this ability on the facebook app and it seems very appealing and user friendly. I have my LongListSelector with items bound to it, and an appbar already in code behind. What is the missing key to enable such a feature?
You just need to figure out when the user is scrolling and in what direction. Here's a great article with example code. Detecting WP8 LongListSelector’s end of scroll. You can modify it to the point where it does exactly what you want.
However, if I was going do it, I would take a more direct route. I would derived my own LLS and attach a property to the value of the scrollbar. Something like this :)
public class MyLLS : LongListSelector, INotifyPropertyChanged
{
// implement the INotify
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// dat grab doe
sb = this.GetTemplateChild("VerticalScrollBar") as System.Windows.Controls.Primitives.ScrollBar;
sb.ValueChanged += sb_ValueChanged;
}
void sb_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
// an animation has happen and they have moved a significance distance
// set the new value
ScrollValue = e.NewValue;
// determine scroll direction
if(e.NewValue > e.OldValue)
{
scroll_direction_down = true;
}
else
{
scroll_direction_down = false;
}
}
public System.Windows.Controls.Primitives.ScrollBar sb;
private bool scroll_direction_down = false; // or whatever default you want
public bool ScrollDirectionDown
{ get { return scroll_direction_down; } }
public double ScrollValue
{
get
{
if (sb != null)
{
return sb.Value;
}
else
return 0;
}
set
{
sb.Value = value;
NotifyPropertyChanged("ScrollValue");
}
}
}
Now you know the exact scroll position. You can even get the top and bottom value by doing
double min = this.sb.Minimum;
double max = this.sb.Maximum;
Now bind that ScrollDirectionDown property to a converter to your AppBar visibility and you'll have your goals met.
If you can't bind then you have to do a callback to update the visibility. But if you want something more simple just hook it up to the ManipulationStateChanged event of the custom LLS.
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
}
private void lls_ManipulationStateChanged(object sender, EventArgs e)
{
if (lls.ScrollDirectionDown)
{
ApplicationBar.IsVisible = false;
}
else
{
ApplicationBar.IsVisible = true;
}
}
}
So for that you have to detect when the longlistselector starts scrolling. For that to achieve there's a similar thread here:
Windows Phone 8 Long List Selector - scroll to bottom after data loaded async
In the DoAndScroll method you could simply include the code to minimize the AppBar.
Within your xaml code of your appbar, change the mode to Minimized.
<shell:ApplicationBar Mode="Minimized" Opacity="1.0" IsMenuEnabled="True" IsVisible="True"></>
Thereafter whenever it scrolls back up, make the Mode of the AppBarto Default.
Or else have a look at this to detect the bottom of the longlistselector.
Detecting WP8 LongListSelector’s end of scroll
I have a user control that I load into a MainWindow at runtime. I cannot get a handle on the containing window from the UserControl.
I have tried this.Parent, but it's always null. Does anyone know how to get a handle to the containing window from a user control in WPF?
Here is how the control is loaded:
private void XMLLogViewer_MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem application = sender as MenuItem;
string parameter = application.CommandParameter as string;
string controlName = parameter;
if (uxPanel.Children.Count == 0)
{
System.Runtime.Remoting.ObjectHandle instance = Activator.CreateInstance(Assembly.GetExecutingAssembly().FullName, controlName);
UserControl control = instance.Unwrap() as UserControl;
this.LoadControl(control);
}
}
private void LoadControl(UserControl control)
{
if (uxPanel.Children.Count > 0)
{
foreach (UIElement ctrl in uxPanel.Children)
{
if (ctrl.GetType() != control.GetType())
{
this.SetControl(control);
}
}
}
else
{
this.SetControl(control);
}
}
private void SetControl(UserControl control)
{
control.Width = uxPanel.Width;
control.Height = uxPanel.Height;
uxPanel.Children.Add(control);
}
Try using the following:
Window parentWindow = Window.GetWindow(userControlReference);
The GetWindow method will walk the VisualTree for you and locate the window that is hosting your control.
You should run this code after the control has loaded (and not in the Window constructor) to prevent the GetWindow method from returning null. E.g. wire up an event:
this.Loaded += new RoutedEventHandler(UserControl_Loaded);
I'll add my experience. Although using the Loaded event can do the job, I think it may be more suitable to override the OnInitialized method. Loaded occurs after the window is first displayed. OnInitialized gives you chance to make any changes, for example, add controls to the window before it is rendered.
Use VisualTreeHelper.GetParent or the recursive function below to find the parent window.
public static Window FindParentWindow(DependencyObject child)
{
DependencyObject parent= VisualTreeHelper.GetParent(child);
//CHeck if this is the end of the tree
if (parent == null) return null;
Window parentWindow = parent as Window;
if (parentWindow != null)
{
return parentWindow;
}
else
{
//use recursion until it reaches a Window
return FindParentWindow(parent);
}
}
I needed to use the Window.GetWindow(this) method within Loaded event handler. In other words, I used both Ian Oakes' answer in combination with Alex's answer to get a user control's parent.
public MainView()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainView_Loaded);
}
void MainView_Loaded(object sender, RoutedEventArgs e)
{
Window parentWindow = Window.GetWindow(this);
...
}
If you are finding this question and the VisualTreeHelper isn't working for you or working sporadically, you may need to include LogicalTreeHelper in your algorithm.
Here is what I am using:
public static T TryFindParent<T>(DependencyObject current) where T : class
{
DependencyObject parent = VisualTreeHelper.GetParent(current);
if( parent == null )
parent = LogicalTreeHelper.GetParent(current);
if( parent == null )
return null;
if( parent is T )
return parent as T;
else
return TryFindParent<T>(parent);
}
This approach worked for me but it is not as specific as your question:
App.Current.MainWindow
How about this:
DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);
public static class ExVisualTreeHelper
{
/// <summary>
/// Finds the visual parent.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sender">The sender.</param>
/// <returns></returns>
public static T FindVisualParent<T>(DependencyObject sender) where T : DependencyObject
{
if (sender == null)
{
return (null);
}
else if (VisualTreeHelper.GetParent(sender) is T)
{
return (VisualTreeHelper.GetParent(sender) as T);
}
else
{
DependencyObject parent = VisualTreeHelper.GetParent(sender);
return (FindVisualParent<T>(parent));
}
}
}
I've found that the parent of a UserControl is always null in the constructor, but in any event handlers the parent is set correctly. I guess it must have something to do with the way the control tree is loaded. So to get around this you can just get the parent in the controls Loaded event.
For an example checkout this question WPF User Control's DataContext is Null
Another way:
var main = App.Current.MainWindow as MainWindow;
It's working for me:
DependencyObject GetTopLevelControl(DependencyObject control)
{
DependencyObject tmp = control;
DependencyObject parent = null;
while((tmp = VisualTreeHelper.GetParent(tmp)) != null)
{
parent = tmp;
}
return parent;
}
This didn't work for me, as it went too far up the tree, and got the absolute root window for the entire application:
Window parentWindow = Window.GetWindow(userControlReference);
However, this worked to get the immediate window:
DependencyObject parent = uiElement;
int avoidInfiniteLoop = 0;
while ((parent is Window)==false)
{
parent = VisualTreeHelper.GetParent(parent);
avoidInfiniteLoop++;
if (avoidInfiniteLoop == 1000)
{
// Something is wrong - we could not find the parent window.
break;
}
}
Window window = parent as Window;
window.DragMove();
If you just want to get a specific parent, not only the window, a specific parent in the tree structure, and also not using recursion, or hard break loop counters, you can use the following:
public static T FindParent<T>(DependencyObject current)
where T : class
{
var dependency = current;
while((dependency = VisualTreeHelper.GetParent(dependency) ?? LogicalTreeHelper.GetParent(dependency)) != null
&& !(dependency is T)) { }
return dependency as T;
}
Just don't put this call in a constructor (since the Parent property is not yet initialized). Add it in the loading event handler, or in other parts of your application.
DependencyObject parent = ExVisualTreeHelper.FindVisualParent<UserControl>(this);
DependencyObject GetTopParent(DependencyObject current)
{
while (VisualTreeHelper.GetParent(current) != null)
{
current = VisualTreeHelper.GetParent(current);
}
return current;
}
DependencyObject parent = GetTopParent(thisUserControl);
The Window.GetWindow(userControl) will return the actual window only after the window was initialized (InitializeComponent() method finished).
This means, that if your user control is initialized together with its window (for instance you put your user control into the window's xaml file), then on the user control's OnInitialized event you will not get the window (it will be null), cause in that case the user control's OnInitialized event fires before the window is initialized.
This also means that if your user control is initialized after its window, then you can get the window already in the user control's constructor.
Gold plated edition of the above (I need a generic function which can infer a Window within the context of a MarkupExtension:-
public sealed class MyExtension : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider) =>
new MyWrapper(ResolveRootObject(serviceProvider));
object ResolveRootObject(IServiceProvider serviceProvider) =>
GetService<IRootObjectProvider>(serviceProvider).RootObject;
}
class MyWrapper
{
object _rootObject;
Window OwnerWindow() => WindowFromRootObject(_rootObject);
static Window WindowFromRootObject(object root) =>
(root as Window) ?? VisualParent<Window>((DependencyObject)root);
static T VisualParent<T>(DependencyObject node) where T : class
{
if (node == null)
throw new InvalidOperationException("Could not locate a parent " + typeof(T).Name);
var target = node as T;
if (target != null)
return target;
return VisualParent<T>(VisualTreeHelper.GetParent(node));
}
}
MyWrapper.Owner() will correctly infer a Window on the following basis:
the root Window by walking the visual tree (if used in the context of a UserControl)
the window within which it is used (if it is used in the context of a Window's markup)
Different approaches and different strategies. In my case I could not find the window of my dialog either through using VisualTreeHelper or extension methods from Telerik to find parent of given type. Instead, I found my my dialog view which accepts custom injection of contents using Application.Current.Windows.
public Window GetCurrentWindowOfType<TWindowType>(){
return Application.Current.Windows.OfType<TWindowType>().FirstOrDefault() as Window;
}
I'm using two classes as Toolbox derived from ItemsControl and ToolboxItem derived from ContnentControl
// Implements ItemsControl for ToolboxItems
public class Toolbox : ItemsControl
{
// Defines the ItemHeight and ItemWidth properties of
// the WrapPanel used for this Toolbox
public Size ItemSize
{
get { return itemSize; }
set { itemSize = value; }
}
private Size itemSize = new Size(50, 50);
// Creates or identifies the element that is used to display the given item.
protected override DependencyObject GetContainerForItemOverride()
{
return new ToolboxItem();
}
// Determines if the specified item is (or is eligible to be) its own container.
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is ToolboxItem);
}
}
// Represents a selectable item in the Toolbox/>.
public class ToolboxItem : ContentControl
{
// caches the start point of the drag operation
private Point? dragStartPoint = null;
static ToolboxItem()
{
// set the key to reference the style for this control
FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(
typeof(ToolboxItem), new FrameworkPropertyMetadata(typeof(ToolboxItem)));
}
protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseDown(e);
this.dragStartPoint = new Point?(e.GetPosition(this));
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton != MouseButtonState.Pressed)
this.dragStartPoint = null;
if (this.dragStartPoint.HasValue)
{
// XamlWriter.Save() has limitations in exactly what is serialized,
// see SDK documentation; short term solution only;
string xamlString = XamlWriter.Save(this.Content);
DragObject dataObject = new DragObject();
dataObject.Xaml = xamlString;
WrapPanel panel = VisualTreeHelper.GetParent(this) as WrapPanel;
if (panel != null)
{
// desired size for DesignerCanvas is the stretched Toolbox item size
double scale = 1.3;
dataObject.DesiredSize = new Size(panel.ItemWidth * scale, panel.ItemHeight * scale);
}
DragDrop.DoDragDrop(this, dataObject, DragDropEffects.Copy);
e.Handled = true;
}
}
}
// Wraps info of the dragged object into a class
public class DragObject
{
// Xaml string that represents the serialized content
public String Xaml { get; set; }
// Defines width and height of the DesignerItem
// when this DragObject is dropped on the DesignerCanvas
public Size? DesiredSize { get; set; }
}
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DesignerItem ni = new DesignerItem();
ni.AllowDrop = true;
ni.ToolTip = "tooltip goes here";
ni.Width = 100;
ni.Height = 200;
ni.Background = Brushes.Red;
//somehow code should introduce the object shape as object. in the sample on codeproject it is reading shape information from xaml but I need to add it from code behind.
Toolbox tb = new Toolbox();
tb.Items.Add(ni);
AddChild() method from ItemsControl must be the method to add a new object on a toolbox. However, when I instantiate an object from this class it does not give me this method. For simplicity I only want to add a rectangle shape on it this would allow me to to drag/drop it on canvas. So question is how I can add a Rectangle on toobox.
Any help is appreciated.
Thanks.
Amit
Solution:
var TheToolbar = ToolboxContainer.Content as Toolbox;
// Instantiate a ToolboxItem
ToolboxItem TheToolboxItem = new ToolboxItem();
Rectangle myRect = new Rectangle();
myRect.StrokeThickness = 1;
myRect.Stroke = some value for stroke;
myRect.Fill = some value for filling the object;
myRect.IsHitTestVisible = false;
//add to Toolbar
TheToolboxItem.Content= myRect;
TheToolbar.Items.Add(TheToolboxItem);
AddChild() is a protected method ( = accessible only from itself or from an inherited class). Try to use a code like this instead:
Toolbox tb = new Toolbox();
tb.Items.Add(myNewItem);
After you instantiate your objects, you need to add them as children to your canvas/parent element.
myParentElement.Children.Add(tb);
If you're doing it, include it in your example code.