WP7 Auto Grow ListBox upon reaching the last item - c#

I'm trying to achieve an effect where more items are appended to the list when the user scrolls down to the last item. I haven't found a way to determine if the user has scrolled to the end of the list. I don't see a event on ListBox that is fired when the user reaches the bottom of the list. Something that tells me when an item has been scrolled into view would be great, but as far as I can tell, there is nothing like that.
Is this even possible in WP7?
Edit: Another way of saying this is, can we detect when a list has "bounced"?

Daniel Vaughan has posted an example of how to detect for this at http://danielvaughan.orpius.com/post/Scroll-Based-Data-Loading-in-Windows-Phone-7.aspx

It isn't super easy to get going since there are a lot of moving parts, but here is what you can do, assuming you want a short list that loads more from your data as you get scrolling down, similar to a lot of twitter apps, etc.
Write your own subclass of ObservableCollection that only offers up a few items (like 20), keeping the rest held back until requested
Hook up to the scroll viewer (inside the listbox or container) and its visual state changed events, you can get the NotScrolling and Scrolling changes; for an example see this code by ptorr
When scrolling stops, use viewer scroll extensions code to see where things are extended (at the bottom or not) or just the raw scroll viewer properties to see if it is extended to the bottom
If so, trigger your observable collection to release another set of items.
Sorry I don't have a complete sample ready to blog yet. Good luck!

I've just implemented this for Overflow7.
The approach I took was similar to http://blog.slimcode.com/2010/09/11/detect-when-a-listbox-scrolls-to-its-end-wp7/
However, instead of using a Style I did the hook up in code.
Basically derived my parent UserControl from:
public class BaseExtendedListUserControl : UserControl
{
DependencyProperty ListVerticalOffsetProperty = DependencyProperty.Register(
"ListVerticalOffset",
typeof(double),
typeof(BaseExtendedListUserControl),
new PropertyMetadata(new PropertyChangedCallback(OnListVerticalOffsetChanged)));
private ScrollViewer _listScrollViewer;
protected void EnsureBoundToScrollViewer()
{
if (_listScrollViewer != null)
return;
var elements = VisualTreeHelper.FindElementsInHostCoordinates(new Rect(0,0,this.Width, this.Height), this);
_listScrollViewer = elements.Where(x => x is ScrollViewer).FirstOrDefault() as ScrollViewer;
if (_listScrollViewer == null)
return;
Binding binding = new Binding();
binding.Source = _listScrollViewer;
binding.Path = new PropertyPath("VerticalOffset");
binding.Mode = BindingMode.OneWay;
this.SetBinding(ListVerticalOffsetProperty, binding);
}
public double ListVerticalOffset
{
get { return (double)this.GetValue(ListVerticalOffsetProperty); }
set { this.SetValue(ListVerticalOffsetProperty, value); }
}
private static void OnListVerticalOffsetChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
BaseExtendedListUserControl control = obj as BaseExtendedListUserControl;
control.OnListVerticalOffsetChanged();
}
private void OnListVerticalOffsetChanged()
{
OnListVerticalOffsetChanged(_listScrollViewer);
}
protected virtual void OnListVerticalOffsetChanged(ScrollViewer s)
{
// do nothing
}
}
this then meant that in the user control itself I could just use:
protected override void OnListVerticalOffsetChanged(ScrollViewer viewer)
{
// Trigger when at the end of the viewport
if (viewer.VerticalOffset >= viewer.ScrollableHeight)
{
if (MoreClick != null)
{
MoreClick(this, new RoutedEventArgs());
}
}
}
private void ListBox1_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
EnsureBoundToScrollViewer();
}
The "hacky" thing here was that I had to use ListBox1_ManipulationCompleted and VisualTreeHelper to find my ScrollViewer - I'm sure there are better ways...

Have a look at this detect Listbox compression state from msdn blog

Use the DeferredLoadListBox.

Related

Copying a TabItem with an MVVM structure

This is an attempt to expand on this question. In my WPF program I've been cloning tabItems by using an XamlWriter in a function called TrycloneElement. I originally found this function here, but the function can also be viewed in the link to my previous question.
Now that I am beginning to worry about functionality inside my program, I found that the TrycloneElement function does not replicate any code-behind functionality assigned to the tabItem that it is cloning.
Because of High Core's link and comment on my earlier question I decided to start implementing functionality on my tabItems through Data Binding with my ViewModel.
Here is a sample of a command that I've implemented:
public viewModel()
{
allowReversing = new Command(allowReversing_Operations);
}
public Command AllowReversing
{
get { return allowReversing; }
}
private Command allowReversing;
private void allowReversing_Operations()
{
//Query for Window1
var mainWindow = Application.Current.Windows
.Cast<Window1>()
.FirstOrDefault(window => window is Window1) as Window1;
if (mainWindow.checkBox1.IsChecked == true) //Checked
{
mainWindow.checkBox9.IsEnabled = true;
mainWindow.groupBox7.IsEnabled = true;
}
else //UnChecked
{
mainWindow.checkBox9.IsEnabled = false;
mainWindow.checkBox9.IsChecked = false;
mainWindow.groupBox7.IsEnabled = false;
}
}
*NOTE: I know that I cheated and interacted directly with my View in the above code, but I wasn't sure how else to run those commands. If it is a problem, or there is another way, please show me how I can run those same commands without interacting with the View like I did.
Now to the question:
After changing my code and adding the commands to my ViewModel, the TrycloneElement function no longer works. At run time during the tab clone I receive an XamlParseException on line, object x = XamlReader.Load(xmlReader); that reads:
I'm fine with ditching the function if there is a better way and I don't need it anymore. But ultimately, how do I take a tabItem's design and functionality and clone it? (Please keep in mind that I really am trying to correct my structure)
Thank you for your help.
Revision of Leo's answer
This is the current version of Leo's answer that I have compiling. (There were some syntax errors)
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
Here is my example of a properly-implemented dynamic TabControl in WPF.
The main idea is that each Tab Item is a separate widget that contains its own logic and data, which is handled by the ViewModel, while the UI does what the UI must do: show data, not contain data.
The bottom line is that all data and functionality is managed at the ViewModel / Model levels, and since the TabControl is bound to an ObservableCollection, you simply add another element to that Collection whenever you need to add a new Tab.
This removes the need for "cloning" the UI or do any other weird manipulations with it.
1.) To fix that XamlParseException, make sure you have a public constructor like an empty one, you probably defined a constructor and when you tried to serialize that object and deserialize it can't. You have to explicitly add the default constructor.
2.) I don't like the word clone, but I'd say, when they want to copy. I'll manually create a new tab item control then do reflection on it.
I have this code that I made
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] {new PropertyFilterAttribute(PropertyFilterOptions.SetValues)})
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd where dpd != null select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly))
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
So it would be like
var newTabItem = new TabItem();
newTabItem.CopyPropertiesFrom(masterTab);

Overriding OnDoubleClick() event on a List View

I have a MyListView class that is inheriting from ListView, and is overriding OnDragDrop() (and the other necessary events to implement drag and drop). When I place two of these MyListviews on a form I am able to drag an item from one of them and drop it to the other one. This part works.
Now I want to override OnDoubleClick() to that class such that again if I place two of these MyListViews on a form and double clicked on one of them, the item gets removed from that and gets added to the other one. But I can't get my head around how to do this one.
Could you please give me some ideas? Thanks.
Don't know if you manage the sleection of the item in a particular way, but you can
or after handling double-click look for SelectedItems and act on it
or you can add a code like this using ListViewHitTestInfo class:
private override OnDoubleClick(...)
{
ListViewHitTestInfo hit = this.HitTest(e.Location);
if (hit.Item != null)
{
ListViewItem doubleClickedItem = hit.Item;
}
}
Put the logic in your host form by:
Handle double-click of first ListView
Remove from first ListView
Add to second ListView
Unless you are doing this in many different forms - it's not worth complicating it more than that.
EDIT:
If justified, centralizing can be as easy as adding a method which does the same thing (pseudocode)
public void MyForm_OnListViewDoubleClick(object sender, EventArgs e)
{
MoveListItem(firstListView, secondListView);
}
// ...
public static void MoveListItem(ListView source, ListView destination)
{
var listItem = source.SelectedItem;
source.Remove( listItem );
destination.Add( listItem );
}
Here's the answer to your title
protected override void OnDoubleClick(EventArgs e)
{
base.OnDoubleClick(e);
}
And here is the answer to your question
Using DoubleClick event on a inherited class from ListView
This just links back to your other, very similar question.

When to render custom WPF TextBlock?

When should I be building Inlines in a TextBlock? I have a TextBlock-derived class that, when given text in a certain field, call it MyText, converts the text into a set of inlines when MyText has changed.
Whenever MyText changes, I clear the Inlines and build them, colorizing each word as needed. For this example, consider:
private void MyTextBlock_MyTextChanged(object sender, EventArgs e)
{
Inlines.Clear();
if (!string.IsNullOrEmpty(this.MyText))
{
var run = new Run();
run.Foreground = Brushes.DarkRed;
run.Text = this.MyText;
Inlines.Add(run);
}
}
This has worked very well. However, recently we placed the Control into a DataGrid, and some strange things have started happening. Apparently the DataGrid swaps out the context and for the most part this works. However, when we add or delete data from the DataGrid ItemsSource, something goes awry, and TextChanged doesn't seem like it is called (or at least not called at the same time). MyText can be one value, and the Inlines either blank or a different value.
I think that the place to build the Inlines is NOT during MyTextChanged, but maybe when the rendering of the Control starts. I've also tried when the DataContextChanged, but this does not help.
In my constructor, I have
this.myTextDescriptor = DependencyPropertyDescriptor.FromProperty(
MyTextProperty, typeof(MyTextBlock));
if (this.myTextDescriptor != null)
{
this.myTextDescriptor.AddValueChanged(this, this.MyTextBlock_MyTextChanged);
}
corresponding to a dependency property I have in the class
public string MyText
{
get { return (string)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(string), typeof(MyTextBlock));
private readonly DependencyPropertyDescriptor myTextDescriptor;
Update: If it is any kind of clue, the problem DataGrid cells seem to be the ones that are off-screen when the addition or removal happens. I also tried OnApplyTemplate, but that didn't help.
Update2: Perhaps a better solution might be to create bindable inlines?
DataGrids virtualize their content, so if a row is not visible it will not be loaded. That being the case, have you tried also rebuilding the inlines when the Loaded event fires?

How to disable a checkbox in a checkedlistbox?

I have some items in a CheckedListBox, I want to disable the CheckBox of first item in it.
i.e. I want to disable the first item in the CheckedListBox, because I want to tell the user visually that option is not available.
Combining 2 of the above partial answers worked great for me.
Add your items to the list with:
myCheckedListBox.Items.Add(myItem, myState);
Where myState is CheckState.Indeterminate for items that should be disabled.
Then add an event handler to keep those items from being changed:
myCheckedListBox.ItemCheck += (s, e) => { if (e.CurrentValue == CheckState.Indeterminate) e.NewValue = CheckState.Indeterminate; };
This does not allow you to use 'Indeterminate' in this list for its normal purpose but it does give a look very similar to what one would expect for a disabled item and it provides the correct behavior!
Though this post is pretty old, the last added answer has been submitted in April this year,
and I hope this will help someone.
I was after something similar : a checked list box that behaves like
a lot of installers, which offer a list of options where some features are required and
thus are both checked and disabled.
Thanks to this post (Can I use a DrawItem event handler with a CheckedListBox?)
I managed to do that, subclassing a CheckedListBox control.
As the OP in the linked post states, in the CheckedListBox control the OnDrawItem event is never fired,
so subclassing is necessary.
It's very basic, but it works.
This is what it looks like (the CheckBox above is for comparison) :
NOTE: the disabled item is really disabled : clicking on it has no effects whatsoever (as far as I can tell).
And this is the code :
public class CheckedListBoxDisabledItems : CheckedListBox {
private List<string> _checkedAndDisabledItems = new List<string>();
private List<int> _checkedAndDisabledIndexes = new List<int>();
public void CheckAndDisable(string item) {
_checkedAndDisabledItems.Add(item);
this.Refresh();
}
public void CheckAndDisable(int index) {
_checkedAndDisabledIndexes.Add(index);
this.Refresh();
}
protected override void OnDrawItem(DrawItemEventArgs e) {
string s = Items[e.Index].ToString();
if (_checkedAndDisabledItems.Contains(s) || _checkedAndDisabledIndexes.Contains(e.Index)) {
System.Windows.Forms.VisualStyles.CheckBoxState state = System.Windows.Forms.VisualStyles.CheckBoxState.CheckedDisabled;
Size glyphSize = CheckBoxRenderer.GetGlyphSize(e.Graphics, state);
CheckBoxRenderer.DrawCheckBox(
e.Graphics,
new Point(e.Bounds.X + 1, e.Bounds.Y + 1), // add one pixel to align the check gliph properly
new Rectangle(
new Point(e.Bounds.X + glyphSize.Width + 3, e.Bounds.Y), // add three pixels to align text properly
new Size(e.Bounds.Width - glyphSize.Width, e.Bounds.Height)),
s,
this.Font,
TextFormatFlags.Left, // text is centered by default
false,
state);
}
else {
base.OnDrawItem(e);
}
}
public void ClearDisabledItems() {
_checkedAndDisabledIndexes.Clear();
_checkedAndDisabledItems.Clear();
this.Refresh();
}
}
Use it like this:
checkedListBox.Items.Add("Larry");
checkedListBox.Items.Add("Curly");
checkedListBox.Items.Add("Moe");
// these lines are equivalent
checkedListBox.CheckAndDisable("Larry");
checkedListBox.CheckAndDisable(0);
Hope this can help someone.
Disabling items isn't a great idea, the user will have no good feedback that click the check box won't have any effect. You cannot use custom drawing to make it obvious. Best thing to do is to simply omit the item.
You can however easily defeat the user with the ItemCheck event:
private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e) {
if (e.Index == 0) e.NewValue = e.CurrentValue;
}
To disable any particular item use following:
checkedListBox1.SetItemCheckState(0, CheckState.Indeterminate);
SetItemCheckState takes index of item and CheckState Enum
Indeterminate is used to show shaded appearance
I know it has been a while, but I found this in my search for a list box and thought I would add it to the discussion.
If you have a listbox and want to disable all of the checkboxes so they cannot be clicked, but not disable the control so the user can still scroll etc. you can do this:
listbox.SelectionMode = SelectionMode.None
The CheckedListBox will not work in this way. CheckedListBox.Items is a collection of strings so they cannot be "disabled" as such.
Here are some discussions about possible solutions that might help you: here and here.
This works for me:
checkedListBox1.SelectionMode = SelectionMode.None;
Which means no items can be selected
None: No items can be selected.
For more info, you can check it here: SelectionMode Enumeration.
The solution is to use the event ItemChecking:
_myCheckedListBox.ItemChecking += (s, e) => e.Cancel = true;
This will cancel all the checking on every item, but you can always do more refined solution but testing the current .SelectedItem
Here's how I did it in a helpdesk application I wrote:
First, I made it so the check box was greyed out as I added it to the list during form load:
private void frmMain_Load(object sender, EventArgs e)
{
List<string> grpList = new List<string>();
ADSI objADSI = new ADSI();
grpList = objADSI.fetchGroups();
foreach (string group in grpList)
{
if (group == "SpecificGroupName")
{
chkLst.Items.Add(group, CheckState.Indeterminate);
}
else
{
chkLst.Items.Add(group);
}
}
Then I used an event so that when clicked it ensures it stays clicked:
private void chkLst_SelectedIndexChanged(object sender, EventArgs e)
{
if (chkLst.SelectedItem.ToString() == "SpecificGroupName")
{
chkLst.SetItemCheckState(chkLst.SelectedIndex, CheckState.Indeterminate);
}
}
The idea here is that on my form it's set so that the box checks on item click/select. This way I could kill two birds with one stone. I could keep this event from causing problems when the item is first checked and added during form load. Plus making it check on select allows me to use this event instead of the item checked event. Ultimately the idea is to keep it from messing up during the load.
You'll also notice that it doesn't matter what the index number is, that variable is unknown because in my app it's grabbing a list of groups from AD that exist in a specific OU.
As to whether this is a good idea or not, that's dependent on the situation. I have another app where the item to disable is dependent on another setting. In this app I just want the helpdesk to see that this group is required so they don't go removing them from it.
Try Below Code:
Private Sub CheckedListBox1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles CheckedListBox1.MouseUp
If (Condition) Then
Me.CheckedListBox1.SelectedIndex = -1
End If
End Sub
I think an alternative solution, is using Telerik components.
A RadListControl can give you that option:

Silverlight ScrollViewer Not Scrolling

enter code hereI have a ScrollViewer in Silverlight that is not scrolling vertically whenever I call the ScrollToVerticalOffset method from the code behind.
Basically, I have my View (UserControl) that contains the ScrollViewer. I invoke an action from my ViewModel that triggers an event in the View's code-behind that sets the VerticalOffset to a specific value.
First of all, I know this is very ugly. Ideally I wish that I could have an attachable property that I could bind to a property in my ViewModel, that, when set, would cause the VerticalOffset property (which I know is read-only) to be updated, and the ScrollViewer to scroll.
The ScrollViewer contains dynamic content. So, if the user is viewing content in the ScrollViewer, and scrolls half-way down, and then clicks on a button, new content is loaded into the ScrollViewer. The problem is that the ScrollViewer's vertical offset doesn't get reset, so the user has to scroll up to read the content. So, my solution was to be able to control the vertical offset from the ViewModel, and I have racked my brain and can't come up with a viable solution, so I am looking for someone to help, please.
By the way - I have included code from a class I put together for an attachable property. This property binds to a property in my ViewModel. When I set the property in the ViewModel, it correctly triggers the PropertyChanged callback method in this class, which then calls the ScrollToVerticalOffset method for the ScrollViewer, but the ScrollViewer still doesn't scroll.
public class ScrollViewerHelper
{
public static readonly DependencyProperty BindableOffsetProperty =
DependencyProperty.RegisterAttached("BindableOffset", typeof(double), typeof(ScrollViewerHelper),
new PropertyMetadata(OnBindableOffsetChanged));
public static double GetBindableOffset(DependencyObject d)
{
return (double)d.GetValue(BindableOffsetProperty);
}
public static void SetBindableOffset(DependencyObject d, double value)
{
d.SetValue(BindableOffsetProperty, value);
}
private static void OnBindableOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollViewer scrollViewer = d as ScrollViewer;
if (scrollViewer != null)
{
scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}
}
}
This approach is a little bit funky in my opinion, as I think of both a ScrollViewer and a VerticalScrollOffset as "View" entities that should have very little (or nothing) to do with a ViewModel. It seems like this might be forcing MVVM a little too much, and creating a lot of extra work in creating an attached dependency property and basically trying to keep a bound Offset ViewModel property in sync with the readonly VerticalScrollOffset of the ScrollViewer.
I am not exactly sure of what you are trying to achieve, but it sounds like you are trying to scroll to a specified offset when some dynamic element is added to the underlying panel of your ScrollViewer. Personally, I would just want to handle this behavior with a little bit of code in my View and forget about tying it to the ViewModel.
One really nice way to do this type of thing in Silverlight 3 is with Blend behaviors. You write a little bit of behavior code in C# and then can attach it declaratively to an element in XAML. This keeps it reusable and out of your code-behind. Your project will have to reference the System.Windows.Interactivity DLL which is part of the Blend SKD.
Here's a simple example of a simple Blend behavior you could add to a ScrollViewer which scrolls to a specified offset whenever the size of the underlying content of the ScrollViewer changes:
public class ScrollToOffsetBehavior : Behavior<ScrollViewer>
{
private FrameworkElement contentElement = null;
public static readonly DependencyProperty OffsetProperty = DependencyProperty.Register(
"Offset",
typeof(double),
typeof(ScrollToOffsetBehavior),
new PropertyMetadata(0.0));
public double Offset
{
get { return (double)GetValue(OffsetProperty); }
set { SetValue(OffsetProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
if (this.AssociatedObject != null)
{
this.AssociatedObject.Loaded += new RoutedEventHandler(AssociatedObject_Loaded);
}
}
protected override void OnDetaching()
{
base.OnDetaching();
if (this.contentElement != null)
{
this.contentElement.SizeChanged -= contentElement_SizeChanged;
}
if (this.AssociatedObject != null)
{
this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
}
}
void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
this.contentElement = this.AssociatedObject.Content as FrameworkElement;
if (this.contentElement != null)
{
this.contentElement.SizeChanged += new SizeChangedEventHandler(contentElement_SizeChanged);
}
}
void contentElement_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.AssociatedObject.ScrollToVerticalOffset(this.Offset);
}
}
You could then apply this behavior to the ScrollViewer in XAML (and specify an offset of 0 to scroll back to the top):
<ScrollViewer>
<i:Interaction.Behaviors>
<local:ScrollToOffsetBehavior Offset="0"/>
</i:Interaction.Behaviors>
...Scroll Viewer Content...
</ScrollViewer>
This would be assuming that you always want to scroll to the offset whenever the content size changes. This may not be exactly what you are looking for, but it is one example of how something like this can be done in the view using a behavior.

Categories