Simple question...
Is it possible to scroll ItemsControl always only by one item?
edited
Ok i added dependency property for VerticalOffest of ScrollView
var _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);
AND
DependencyProperty ListVerticalOffsetProperty = DependencyProperty.Register(
"ListVerticalOffset",
typeof(double),
typeof(SubscriptionFeed),
new PropertyMetadata(0.0, OnListVerticalOffsetChanged));
public double ListVerticalOffset
{
get { return (double)this.GetValue(ListVerticalOffsetProperty); }
set { this.SetValue(ListVerticalOffsetProperty, value); }
}
private static void OnListVerticalOffsetChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
// stop the scrolling by condition
}
BUT
VerticalOffset changes with relatively low frequency, with faster scrolling commonly skips the item...
video: http://screenr.com/mc3s
Related
I am trying to make a custom renderer for an editor that changes the "return" key to a "done" button and fires the Completed event when you tap it instead of typing a newline. The code in OnElementChanged() is being hit, but it's not doing anything. The "return" key is still a "return" key and it still types newlines instead of making the editor go out of focus. What am I doing wrong?
Here is the class for the custom editor (in the .NET project):
using Xamarin.Forms;
namespace Partylist.Custom_Controls
{
public class ChecklistEditor : Editor
{
}
}
Here is the custom renderer for Android:
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Views.InputMethods;
using Android.Widget;
using Partylist.Custom_Controls;
using Partylist.Droid.Custom_Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(ChecklistEditor), typeof(ChecklistEditorRenderer))]
namespace Partylist.Droid.Custom_Renderers
{
class ChecklistEditorRenderer : EditorRenderer
{
// Constructor because it needs to exist.
public ChecklistEditorRenderer(Context context) : base(context)
{
}
// This gets overridden so I can change what I want to change.
protected override void OnElementChanged(ElementChangedEventArgs
<Editor> e)
{
// Make it do what is should normally do so it will exist.
base.OnElementChanged(e);
// Make the "Return" button on the keyboard be a "Done" button.
Control.ImeOptions = ImeAction.Done;
// Make the thing watch for when the user hits the "Return" button.
Control.EditorAction += OnEditorAction;
}
// This makes the "Return" button fire the "Completed" event
// instead of typing a newline.
private void OnEditorAction(object sender, TextView
.EditorActionEventArgs e)
{
e.Handled = false;
if (e.ActionId == ImeAction.Done)
{
Control.ClearFocus();
e.Handled = true;
}
}
}
}
Here is the custom renderer for iOS:
using Foundation;
using Partylist.Custom_Controls;
using Partylist.iOS.Custom_Renderers;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ChecklistEditor), typeof(ChecklistEditorRenderer))]
namespace Partylist.iOS.Custom_Renderers
{
class ChecklistEditorRenderer : EditorRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs
<Editor> e)
{
base.OnElementChanged(e);
Control.ReturnKeyType = UIReturnKeyType.Done;
}
protected override bool ShouldChangeText(UITextView textView,
NSRange range, string text)
{
if (text == "\n")
{
textView.ResignFirstResponder();
return false;
}
return true;
}
}
}
The code-behind for the page where I'm using these custom renderers (there's nothing in the XAML that should conflict with it, I think, but I'll add it to the post if people want to make sure):
using Partylist.Custom_Controls;
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Partylist.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ChecklistPage : ContentPage
{
// Struct for items on the checklist.
struct Item
{
public ChecklistEditor ItemEditor { get; set; }
public CheckBox ItemCheckbox { get; set; }
}
// Create a list of contact structs to populate the ListView.
ObservableCollection<Item> items;
// Flag for when an item is added to the list.
bool itemAdded = false;
// Constructor.
public ChecklistPage()
{
// Whatever setup stuff it was going to do anyway.
InitializeComponent();
// Set the label's BindingContext to the
// App class so it can update its text.
tipLabel.BindingContext = (App)App.Current;
}
// Override for OnAppearing().
protected override void OnAppearing()
{
// Makes the page appear.
base.OnAppearing();
// Set the page's title to be the name of the selected list.
Title = App.selectedList.Name;
// Make a toolbar item appear to access the Main Checklist
// unless we are already there.
if (App.selectedList.ListFile.Name.EndsWith(".mchec"))
{
ToolbarItems.Remove(MainChecklistButton);
}
// Set the binding context of the page to itself.
BindingContext = this;
// Start the timer for the tips banner if it is stopped.
App.tipTimer.Start();
// Set the banner's text to the current tip's sumamry.
tipLabel.Text = ((App)App.Current).CurrentTip.Summary;
OnPropertyChanged("CurrentTip");
// Subscribe the OnTipUpdate function to the tipUpdate event in the app
// class.
App.TipUpdate += OnTipUpdate;
// Make the ObservableCOllection reference something.
items = new ObservableCollection<Item>();
// Open a stream to the list that we want to display.
using (StreamReader listReader = new StreamReader(App.selectedList
.ListFile.FullName))
{
// Loop through the file and read data into the list.
while (!listReader.EndOfStream)
{
// Create a blank item.
Item newItem = new Item()
{
ItemEditor = new ChecklistEditor()
{
Text = listReader.ReadLine(),
Placeholder = "New Item",
IsTabStop = true,
AutoSize = EditorAutoSizeOption.TextChanges,
WidthRequest = 310
},
ItemCheckbox = new CheckBox()
{
Color = App.selectedList.ListItemColor,
IsChecked = bool.Parse(listReader.ReadLine())
}
};
// Subscribe OnCompleted() to the new item's "Completed"
// event.
newItem.ItemEditor.Completed += OnCompleted;
// Subscribe OnTextChanged() to the new item's
// "TextChanged" event.
newItem.ItemEditor.TextChanged += OnTextChanged;
// Add the new item to the list.
items.Add(newItem);
// Make the ListView update.
ChecklistView.ItemsSource = items;
OnPropertyChanged("contacts");
}
// Once everything is loaded, close the file.
listReader.Close();
}
}
// Override for OnDisappearing().
protected override void OnDisappearing()
{
// Makes the page disappear.
base.OnDisappearing();
// Open a stream to the file for the list.
StreamWriter listWriter = new StreamWriter(App.selectedList
.ListFile.FullName);
// Loop through the contacts list to write the contacts to the
// file.
for (int i = 0; i < items.Count; i++)
{
// Write each item to the file.
listWriter.WriteLine(items.ElementAt(i).ItemEditor.Text);
listWriter.WriteLine(items.ElementAt(i).ItemCheckbox.IsChecked);
}
// Close the stream.
listWriter.Close();
}
// Function for when the "Add New Contact" button is clicked.
private void OnAddNewItemClicked(object sender, EventArgs e)
{
// Create a blank item.
Item newItem = new Item()
{
ItemEditor = new ChecklistEditor()
{
Placeholder = "New Item",
IsTabStop = true,
AutoSize = EditorAutoSizeOption.TextChanges,
WidthRequest = 310
},
ItemCheckbox = new CheckBox()
{
Color = App.selectedList.ListItemColor,
IsChecked = false
}
};
// Subscribe OnCompleted() to the new item's "Completed"
// event.
newItem.ItemEditor.Completed += OnCompleted;
// Subscribe OnTextChanged() to the new item's
// "TextChanged" event.
newItem.ItemEditor.TextChanged += OnTextChanged;
// Add the new contact to the list.
items.Add(newItem);
// Set the "itemAdded" flag to true.
itemAdded = true;
// Make the ListView update.
ChecklistView.ItemsSource = items;
OnPropertyChanged("contacts");
// Select the new item.
ChecklistView.SelectedItem = items.ElementAt(items.Count - 1);
}
// Function for when an item is selected, used to set the focus to
// a newly added item in the list.
private async void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
// Only runs this if an item was added (as opposed to being
// read in from the file).
if (itemAdded)
{
if (e.SelectedItem == null) return;
await Task.Delay(100); // Change the delay time if Focus() doesn't work.
((Item)e.SelectedItem).ItemEditor.Focus();
ChecklistView.SelectedItem = null;
itemAdded = false;
}
}
// Function for when the user presses "Return" on the keyboard in
// an editor.
private void OnCompleted(object sender, EventArgs e)
{
// We just want to unfocus the editor.
((Editor)sender).Unfocus();
}
// Function for when the user types anything in the editor, used
// to make sure it resizes.
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
// Makes the cell resize. The cell is the parent of the
// StackLayout which is the parent of the ContentView which is
// the parent of the Editor that fired the event.
((ViewCell)((Editor)sender).Parent.Parent.Parent)
.ForceUpdateSize();
}
}
}
In Android , you need to set Single Line for EditTextView , then it will works .
For example :
...
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
// set single line will works
Control.SetSingleLine();
Control.ImeOptions = ImeAction.Done;
Control.EditorAction += OnEditorAction;
}
private void OnEditorAction(object sender, TextView.EditorActionEventArgs e)
{
e.Handled = false;
if (e.ActionId == ImeAction.Done)
{
Control.ClearFocus();
e.Handled = true;
InputMethodManager imm = (InputMethodManager)Control.Context.GetSystemService(Context.InputMethodService);
imm.HideSoftInputFromWindow(Control.WindowToken, 0);
}
}
...
The effect :
About iOS to achieve that , you can refer to follow code :
[assembly: ExportRenderer(typeof(Editor), typeof(CustomEditorRenderer))]
namespace AppEntryTest.iOS
{
class CustomEditorRenderer : EditorRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
Control.ReturnKeyType = UIReturnKeyType.Done;
}
protected override bool ShouldChangeText(UITextView textView, NSRange range, string text)
{
if (text == "\n")
{
textView.ResignFirstResponder();
return false;
}
return true;
}
}
}
The effect :
====================================Update================================
If need to wrap text in Android , you can set background for EditTextView :
Adding bg_gray_border.xml in Resources/drawable folder :
<?xml version="1.0" encoding="utf-8" ?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffffff" />
<stroke
android:width="1dp"
android:color="#DEDEDE" />
<corners android:radius="6dp" />
</shape>
Used in Renderer class :
...
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
Control.SetSingleLine();
Control.SetBackgroundResource(Resource.Drawable.bg_gray_border);
Control.ImeOptions = ImeAction.Done;
Control.EditorAction += OnEditorAction;
}
...
The effect :
Add wapped text in iOS ,
...
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
Control.ReturnKeyType = UIReturnKeyType.Done;
Control.Layer.BorderColor =UIColor.Gray.CGColor;
Control.Layer.BorderWidth = 1;
Control.Layer.CornerRadius = 5;
}
...
The effect :
Here is the sample project .
While I'm using frame in Mainwindow , initially i hide an item in Mainwindows.
When i pressed a button in frame Page1 , I want to make item in mainwindow as visible.But i can't do it.I tried to updatelayout() , refresh() functions but anything is changed.Anyone has a knowledge about this??
This code is in MainWindow
private void Window_Loaded(object sender, RoutedEventArgs e)
{
müsteributton.IsEnabled = false;
string yer = "Pages/kullanicigiris.xaml";
frame1.Source = new Uri(yer, UriKind.Relative);
frame1.Margin = new Thickness(-175, 0, 0, 0);
}
This code is in kullanicigiris page
private void Dispatcher_Tick(object sender, EventArgs e)
{
i++;
if (i == 2)
{
dispatcher.Stop();
frm1 = new MainWindow();
frm1.frame1 = null;
DependencyObject currParent = VisualTreeHelper.GetParent(this);
while (currParent != null && frm1.frame1 == null)
{
frm1.frame1 = currParent as Frame;
currParent = VisualTreeHelper.GetParent(currParent);
}
// Change the page of the frame.
if (frm1.frame1 != null)
{
frm1.frame1.Source = new Uri("Window1.xaml", UriKind.Relative);
frm1.müsteributton.IsEnabled = true;
}
}
}
Thanks.
You can define a DependencyProperty in the MainWindows.
<TextBlock x:Name="textBlock" Height="399" TextWrapping="Wrap" Text="Show/ Hide" VerticalAlignment="Top" Visibility="{Binding SetVisibility, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
public static readonly DependencyProperty SetVisibilityProperty =
DependencyProperty.Register("SetVisibility", typeof(Visibility), typeof(Mainfreampage), new
PropertyMetadata(Visibility.Visible, null));
public Visibility SetVisibility
{
get { return (Visibility)GetValue(SetVisibilityProperty); }
set { SetValue(SetVisibilityProperty, value); }
}
In your page click event, you can use the following code find the MainWindows and change the DependencyProperty value.
var mw = Application.Current.Windows
.Cast<Mainfreampage>()
.FirstOrDefault(window => window is Mainfreampage) as Mainfreampage;
mw.SetVisibility = Visibility.Hidden;
Your bug is here:
frm1 = new MainWindow();
You are creating a brand new window, and then making your changes in that window.
But: that's not the window the user's looking at!
Taking the approach you've embarked on, your frame code needs to keep track of the Window object it's actually being hosted in, and then use that reference for dealing with the update.
That said, that entire approach is flawed. The navigation should be modeled in a view model data structure, activated via an ICommand object, and optionally via timer (as you seem to be doing here). Frame source and button state can be manipulated through bindings to properties in your view model data structure.
But, at the end of the day, the code you've got should work fine, once you start using the correct Window object.
I need basically an event that triggers at each 200 records loaded, so more data can be loaded until the end of data.
I tried to extend CharmListCell and using the method updateItem like this:
#Override
public void updateItem(Model item, boolean empty) {
super.updateItem(item, empty);
currentItem = item;
if (!empty && item != null) {
update();
setGraphic(slidingTile);
} else {
setGraphic(null);
}
System.out.println(getIndex());
}
But the System.out.println(getIndex()); method returns -1;
I would like to call my backend method when the scroll down gets the end of last fetched block and so on, until get the end of data like the "infinite scroll" technique.
Thanks!
The CharmListCell doesn't expose the index of the underlying listView, but even if it did, that wouldn't be of much help to find out if you are scrolling over the end of the current list or not.
I'd suggest a different approach, which is also valid for a regular ListView, with the advantage of having the CharmListView features (mainly headers and the refresh indicator).
This short sample, created with a single view project using the Gluon IDE plugin and Charm 5.0.0, shows how to create a CharmListView control, and fill it with 30 items at a time. I haven't provided a factory cell, nor the headers, and for the sake of simplicity I'm just adding consecutive integers.
With a lookup, and after the view is shown (so the listView is added to the scene) we find the vertical ScrollBar of the listView, and then we add a listener to track its position. When it gets closer to 1, we simulate the load of another batch of items, with a pause transition that represents a heavy task.
Note the use of the refresh indicator. When new data is added, we scroll back to the first of the new items, so we can keep scrolling again.
public class BasicView extends View {
private final ObservableList<Integer> data;
private CharmListView<Integer, Integer> listView;
private final int batchSize = 30;
private PauseTransition pause;
public BasicView() {
data = FXCollections.observableArrayList();
listView = new CharmListView<>(data);
setOnShown(e -> {
ScrollBar scrollBar = null;
for (Node bar : listView.lookupAll(".scroll-bar")) {
if (bar instanceof ScrollBar && ((ScrollBar) bar).getOrientation().equals(Orientation.VERTICAL)) {
scrollBar = (ScrollBar) bar;
break;
}
}
if (scrollBar != null) {
scrollBar.valueProperty().addListener((obs, ov, nv) -> {
if (nv.doubleValue() > 0.95) {
addBatch();
}
});
addBatch();
}
});
setCenter(new VBox(listView));
}
private void addBatch() {
listView.setRefreshIndicatorVisible(true);
if (pause == null) {
pause = new PauseTransition(Duration.seconds(1));
pause.setOnFinished(f -> {
int size = data.size();
List<Integer> list = new ArrayList<>();
for (int i = size; i < size + batchSize; i++) {
list.add(i);
}
data.addAll(list);
listView.scrollTo(list.get(0));
listView.setRefreshIndicatorVisible(false);
});
} else {
pause.stop();
}
pause.playFromStart();
}
}
Note also that you could benefit from the setOnPullToRefresh() method, at any time. For instance, if you add this:
listView.setOnPullToRefresh(e -> addBatch());
whenever you go to the top of the list and drag it down (on a mobile device), it will make another call to load a new batch of items. Obviously, this is the opposite behavior as the "infinite scrolling", but it is possible as well with the CharmListView control.
I have this annoying problem and I don't know how to solve this.
In Xamarin Forms, I'm trying to draw a dynamic layout, for this I load a list of elements (this works). Now i'm trying to display the label for it, so I loop through all the items and add a label for every item. The problem is that the page stays empty.
Yes I initialized the _layout variable as a StackLayout and I also made a ScrollView, then I set the scrollview's content to the _layout variable. But still my page stays empty. I can't share the actual code but I rewrote it using different names.
private void DrawItems()
{
var items = (List<Item>)_database.GetItems();
foreach(var item in items)
{
DrawItem(item);
}
}
private void DrawItem(Item item)
{
AddLabel(item);
}
private void AddLabel(Item item)
{
if (string.IsNullOrEmpty(item.Text)) return;
var label = new Label
{
Text = (!string.IsNullOrEmpty(item.Number)) ? string.Format("{0}: {1}", item.Number, item.Text) : item.Text,
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label))
};
_layout.Children.Add(label);
}
For some weird reason, when I start debugging (put a break on var label ...) the label get's created but when I put a break on _layout.Children.Add(label), this never gets called.
When changing UI elements, you need to do it on the main thread.
Try this:
Device.BeginInvokeOnMainThread(() =>
{
_layout.Children.Add(label);
}
I've tried the following code, and it works for me. I'm using Xamarin.Forms 1.4.0.6341
public class MyPage : ContentPage
{
StackLayout stack;
public MyPage()
{
var scroll = new ScrollView();
stack = new StackLayout();
var btn = new Button {Text = "Add Label"};
btn.Clicked += (sender, args) => stack.Children.Add(new Label {Text = "Test"});
stack.Children.Add(btn);
AddLabelEverySecond();
scroll.Content = stack;
Content = scroll;
}
private async void AddLabelEverySecond()
{
for (var i = 0; i < 10; i++)
{
await Task.Delay(1000);
stack.Children.Add(new Label { Text = "1 second" });
}
}
}
Is this applicable to your code? Maybe you could tell us where DrawItems is called from?
In a SL4 application i need to restyle my TabItems (actually add a button in the header).
So i took the TabItem's control template from here and added the functionality i wanted.
This seems to work fine, (i could dynamically add tabitems) with one exception:
i think this posted control template is behaving somehow "arbitrary": every time the mouse hoovers over a non selected TabItem header, this gets selected WHITHOUT clicking!! (afaik this is not the default behavior: the user user has to click a header to make this tabitem the selected one).
I tried to find why it is behaving like this, with no luck!
Is there someone who can enlighten my darkness???
Thanks in advance!
Well it turns out the error was not in the control template but in the class, the style was applied to.
In detail: the class the style was applied to is the following (in it you will see my comment about the "wrong behavior"):
public class WorkspaceViewModel : TabItem
{
public WorkspaceViewModel()
{
DefaultStyleKey = typeof(WorkspaceViewModel);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Button closeButtonSel = base.GetTemplateChild("PART_CloseTopSelected") as Button;
Button closeButtonUnsel = base.GetTemplateChild("PART_CloseTopUnSelected") as Button;
if (closeButtonSel != null)
closeButtonSel.Click += new RoutedEventHandler(closeButtonSel_Click);
if (closeButtonUnsel != null)
closeButtonUnsel.Click += new RoutedEventHandler(closeButtonSel_Click);
//this part is causing the effect i was complaining about!
//and has to be removed
this.MouseEnter += delegate(object sender, MouseEventArgs e)
{
IsSelected = true;
};
}
void closeButtonSel_Click(object sender, RoutedEventArgs e)
{
//this is the close request method used in the CloseTabItemCommand
OnRequestClose();
}
#region CloseTabItemCommand
private RelayCommand closeTabItemCommand;
public ICommand CloseTabItemCommand
{
get
{
if (this.closeTabItemCommand == null)
this.closeTabItemCommand = new RelayCommand(p => this.OnRequestClose(), p => this.CanCloseTabItem());
return this.closeTabItemCommand;
}
}
private bool CanCloseTabItem()
{
return true;
}
public event EventHandler RequestClose;
private void OnRequestClose()
{
if (RequestClose != null)
RequestClose(this, EventArgs.Empty);
}
#endregion
}