I want to achieve one-way binding from an ObservableCollection of "struct-like" items to a TextBox that has a TextChanged event. The idea is that as the Comments field of Item accumulates in the TextBox, the TextBox scroll down automatically so that the last line is always in view. The collection is bound to a ListView but I want it bound read-only to the TextBox. I would prefer not to add another method in ResultViewModel but do it in XAML. How would I go about in doing this? TIA
// ViewModel
public class Item
{
public string First { get; set; }
public string Last { get; set; }
public string Comments { get; set; }
}
public class ResultViewModel
{
private ObservableCollection<Item> items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items { get { return items; } }
// member functions
}
public ResultViewModel ViewModel { get; set; }
// What I have
<ListView x:Name="myListView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ItemsSource="{x:Bind ViewModel.Items}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Item">
<StackPanel>
<TextBox Text="{x:Bind First}"/>
<TextBlock Text="{x:Bind Last}"/>
<TextBlock Text="{x:Bind Comments}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
// What I want
<TextBox Text="{x:Bind Comments}"/>
I'm afraid you can't do it with XAML alone.
You can create a behavior which will listen to events and add lines to the textbox when the collection is modified.
Dirty example, you will need to include Microsoft.Xaml.Behaviors.Uwp.Managed package:
public class CommentsBehavior : Behavior
{
public ObservableCollection<string> Comments ... // you will need to make it a dependency property
protected virtual void OnAttached()
{
Comments.CollectionChanged += OnCollectionChanged;
}
protected virtual void OnDetaching()
{
Comments.CollectionChanged -= OnCollectionChanged;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach(string newItem in e.NewItems)
{
((TextBox)AssociatedObject).Text = ((TextBox)AssociatedObject).Text + '\n' + newItem;
}
}
}
}
And for scrolling - UWP C# Scroll to the bottom of TextBox
But why do you want to use textbox for this? Using list makes more sense.
Related
I have model with 3 fields : TItle, Body, Status.
public class Names
{ [PrimaryKey]
public string Title { get; set; }
public string Body { get; set; }
public string Status{ get; set; }}
When user opens the page he can see list of names with fields (Title, Body).
Code of page looks like:
xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class HomePage : ContentPage
{
public ObservableCollection<Models.Names> items { get; set; }
public HomePage()
{
items = new ObservableCollection<Models.Names>();
this.BindingContext = this;
InitializeComponent();
List.ItemSelected += (sender, e) => {
((ListView)sender).SelectedItem = null;
};
List.Refreshing += (sender, e) => {
LoadUsersData();
};
LoadUsersData();
}
public async void LoadUsersData()
{
List.IsRefreshing = true;
var Names= await App.Database.Names.GetItemsAsync();
items.Clear();
foreach (var item in Names)
items.Add(item);
List.IsRefreshing = false;
}
}
xaml
<StackLayout>
<ListView x:Name="List"
HasUnevenRows="True"
ItemsSource="{Binding items}"
IsPullToRefreshEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell
Text="{Binding Title}"
Detail="{Binding Body}"
TextColor="Black"
DetailColor="Gray">
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
In the begin of page I want to add string which shows amount of all items with Status = "New".
How I can do it?
Add a Label that binds to the Count property of your ObservableCollection (it will be notified each time items are added/deleted from your collection):
<Label Text="{Binding items.Count, StringFormat='Status = {0}'}"/>
Update
If you need custom properties, like the number of Names objects with a Status of "new", there are multiple ways to create bindable properties, but one way is to subclass ObservableCollection and add your custom property:
public class MyObservableCollection : ObservableCollection<Names>
{
public MyObservableCollection()
{
CollectionChanged += (object sender, NotifyCollectionChangedEventArgs e) =>
{
OnPropertyChanged(new PropertyChangedEventArgs("NewCount"));
};
}
public int NewCount
{
get { return this.Count((Names arg) => arg.Status == "new"); }
}
}
Now replace your use of ObservableCollection with MyObservableCollection.
public MyObservableCollection items { get; set; }
In your XAML, you can now bind on NewCount:
<Label Text="{Binding items.Count, StringFormat='Status = {0}'}"/>
<Label Text="{Binding items.NewCount, StringFormat='Status = {0}'}"/>
In terms using a BindableProperty instead, there are other SO question/answers already posted and a great blog post:
https://xamarinhelp.com/bindable-properties-xamarin-forms/
I am having List Box which contain the buttons. We can add any no of buttons. I have added the buttons into the list called "AddedButtonList" through c# and bind that list as follow:
<Grid Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Grid.RowSpan="1">
<Grid.Resources>
<DataTemplate DataType="{x:Type viewModel:AddedAction}">
<Button Content="{Binding Title}"
Height="40"
Width="100"
Command="{Binding Command}">
</Button>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding actionsRecordVmObj.AddedActionsList}" Width="Auto">
</ListBox>
</Grid>
we can add any no of buttons by using above code in xaml because i have bind all the properties from code behind.
the code behind is:
public abstract class AddedAction
{
public bool IsDisable { get; set; }
public string Title { get; set; }
public string ButtonIndex { get; set; }
public abstract ICommand Command { get; }
}
public class AddedSourceFileActionVm : AddedAction
{
public ICommand _command;
//constructor
public AddedSourceFileActionVm()
{
Title = "Source File";
_command = new RelayCommand(p => AddedSourceFileActionCommandExecuted(null), p => CanAddedSourceFileActionCommandExecute());
}
All buttons are bind with command
(buttons may be repeat in the list).
I want to get the index of the button(Item from list) which get pressed.
I read many answers some of were saying use AlternationCount but when I want the index in code behind I am not able to do that because I use Command for binding and they showed for click event.
I cant use
private void lstButton_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
int index = _myListBoxName.Items.IndexOf(button.DataContext);
}
because I am using MVVM and I bind all buttons with command.
so please suggest some solution for this.
or in short how to get the index of button which is pressed from list?
Thanks in advance....
Here's how you can easily get the index:
1. Define an abstract event on your AddedAction.
2. Subscribe to the event when creating an AddedAction instance
3. Raise the event when AddedAction.Command is executed
4. Get the index on the event handler
For example:
public abstract class AddedAction
{
//define the event
public abstract event EventHandler CommandExecuted;
public abstract ICommand Command { get; }
//...
}
public class AddedSourceFileActionVm : AddedAction
{
public override event EventHandler CommandExecuted;
private void AddedSourceFileActionCommandExecuted(object obj)
{
//invoke the event
CommandExecuted?.Invoke(this, null);
//...
}
//...
}
public class ActionsRecordVm
{
public List<AddedAction> AddedActionsList { get; } = new List<AddedAction>();
public void AddNewAddedAction()
{
var addedAction = new AddedSourceFileActionVm();
//Subscribe to the event
addedAction.CommandExecuted += AddedAction_CommandExecuted;
AddedActionsList.Add(addedAction);
}
private void AddedAction_CommandExecuted(object sender, EventArgs e)
{
//get the index
int index = AddedActionsList.IndexOf((AddedAction)sender);
//...
}
//...
}
I develop CRUD app for WindowsPhone 8.1. I can add data to ObservableCollection collection and this data is displayed on ListBox. I use MVVM pattern.
Full repository https://github.com/OlegZarevych/CRUD_WP81
View :
<ListBox x:Name="Storage" ItemsSource="{Binding Path=Models, Mode=TwoWay}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="30" Width="450">
<TextBlock x:Name="nameblock" Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And ViewModel class
public class ViewModel
{
public string NewName { get; set; }
public string NewSurname { get; set; }
public int NewAge { get; set; }
public int i=0 ;
public ObservableCollection<DataStorageModel> Models { get; set; }
//Event Handlers
public ICommand CreateClickCommand { get; set; }
public ICommand UpdateClickCommand { get; set; }
public ICommand DeleteClickCommand { get; set; }
public ViewModel()
{
CreateClickCommand = new RelayCommand(arg => CreateClickMethod());
UpdateClickCommand = new RelayCommand(arg => UpdateClickMethod());
DeleteClickCommand = new RelayCommand(arg => DeleteClickMethod());
Models = new ObservableCollection<DataStorageModel>() {};
}
private void CreateClickMethod()
{
Models.Add(new DataStorageModel() { Name = NewName, Surname = NewSurname, Age = NewAge, Count=i++ });
}
private void UpdateClickMethod()
{}
private void DeleteClickMethod()
{}
}
I want to change data and delete it. As i good understand, I need select count from ListBoxItems and delete(update) this count in ObservableCollection.
How can I work with XAML code from ViewModel class ?
How can I initiliaze Storage in ViewModel ?
Or in MVVM is the better way to resolve this problem ?
When you want to delete a model from the ListBox you typically need some way to identify the selected ListBoxItems (or models) that you want to delete; for that, consider having an IsSelected property on your models and bind it to a CheckBox inside the ListBoxItem data template.
Now, when you click on delete, the delete command can then easily look into the Models list and see which items are selected for deletion. After it deletes the items, it can then enumerate over the collection and recalculate the count value for the remaining items and update the field in the view model.
So, you don't have to access the XAML to update the count of the models. If you make the count property mutable then you wouldn't have to reinitialize the storage after you delete items from the list.
I added code t the Model
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Also added checkbox with bindin to View.
<ListBox x:Name="Storage" Background="Gray" FontSize="14" ItemsSource="{Binding Path=Models, Mode=TwoWay}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="60" Width="400" >
<CheckBox x:Name="checkbox" IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock x:Name="nameblock" Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
But IsSelected var doesn't change when I check checkbox in item
Why ?
I am working on my application update and I want to use a new searchbox and I want to show my results like Windows Store .
how can I do this ?
You can use an AutoSuggestBox which is bound to a changing ObservableCollection everytime the Text inside the AutoSuggestBox is changed.
For example, this is your Model:
public class App
{
public ind Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public Image Picture { get; set; }
}
You can implement a method updating an ObservableCollection with a parameter (in this case the search expression) in your ViewModel:
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
SuggestedApps = new ObservableCollection<App>();
SuggestedApps.CollectionChanged += SuggestedApps_CollectionChanged;
}
private void SuggestedApps_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("SuggestedApps");
}
private ObservableCollection<App> suggestedApps;
public ObservableCollection<App> SuggestedApps
{
get
{
return suggestedApps;
}
set
{
suggestedApps = value;
OnPropertyChanged("SuggestedApps");
}
}
public void SuggestForSearch(string searchExpression)
{
SuggestedApps.Clear();
//Assumgin EF as DataSource
//You can use another Search algorithm here instead of String.Contains
foreach(var item in yourDataSource.Apps.Where(x => x.Name.Contains(searchExpression.Trim())))
{
SuggestedApps.Add(item);
}
}
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
In your Xaml code you can use this to bind an AutoSuggestBox to it and define a Template:
<AutoSuggestBox x:Name="AutoSuggestBoxApps" ItemsSource="{Binding SuggestedApps}" TextChanged="AutoSuggestBoxApps_TextChanged">
<AutoSuggestBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Picture}"/>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Category}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</AutoSuggestBox.ItemTemplate>
</AutoSuggestBox>
In the implemetation of the TextChanged-Event you just call the SuggestForSearch Method from your ViewModel:
private void AutoSuggestBoxApps_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
(this.DataContext as ViewModel).SuggestForSearch((sender as AutoSuggestBox).Text);
}
There is a control for UWP named AutoSuggestBox that you should read up on:
https://msdn.microsoft.com/nb-no/library/windows/apps/xaml/windows.ui.xaml.controls.autosuggestbox.aspx
This should give you the tools you need to give the wanted functionality
I am having an absolute headache figuring this out. I badly need some help with this.
I have a listbox populated with items called with a public static void RSS feed class. Once the listbox populates with the databound items, I click on an item and it passes it through to my pivot page. However, when I flick left or right, all I get is the same image. That is my problem, and what I would like to have happen is if the user flicks left, it loads the previous RSS image. I would like it to also go to the next picture if the If the user scrolls right.
The community has been helpful in providing links to some things, or saying to not use the listbox, etc. However while I am new to all of this, I would just like concrete help with the code i have to achieve what I have in mind. It's nothing personal -- I just need to take babysteps with this before I get worked up with other things I have no clue about.
Here is all my relevant code.
Page 1 Xaml:
<ListBox x:Name="listbox" HorizontalContentAlignment="Stretch" ItemsSource="{Binding items}" SelectionChanged="listbox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Stretch="Fill" Height="60" Width="85" Source="{Binding Url}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Page1 C# Code Behind:
namespace Imaged
{
public partial class UserSubmitted : PhoneApplicationPage
{
private const string Myrssfeed = "http://feeds.bbci.co.uk/news/rss.xml";
public UserSubmitted()
{
InitializeComponent();
//This next function calls the RSS service, and returns the (items) and binds it to
//{listbox.ItemsSource = items;}. I am unable to reference the count of the items, or
//the array of it for some reason? The images load once the page loads.
RssService.GetRssItems(Myrssfeed, (items) => { listbox.ItemsSource = items; }, (exception) => { MessageBox.Show(exception.Message); }, null);
}
}
}
Once the listbox fills I am now trying to pass the selection by the user to a pivot page. I want that same image to show up in the pivot, and when the user pivots left or right, it shows the previous image or next image in the collection.
The Pivot Page I am trying to pass this to, XAML:
<Grid x:Name="LayoutRoot" Background="Transparent">
<!--Pivot Control-->
<controls:Pivot Title="{Binding Title}">
<!--Pivot item one-->
<controls:PivotItem x:Name="item1">
<Image Source="{Binding Url}"/> <!--I take it this is causing the pics to be the same?-->
</controls:PivotItem>
<!--Pivot item two-->
<controls:PivotItem x:Name="item2">
<Image Source="{Binding Url}"/>
</controls:PivotItem>
<!--Pivot item three-->
<controls:PivotItem x:Name="item3">
<Image Source="{Binding Url}"/>
</controls:PivotItem>
</controls:Pivot>
</Grid>
The RSS Service Class being called:
namespace WindowsPhone.Helpers
{
public class RssService
{
public static void GetRssItems(string rssFeed, Action<IList<RssItem>> onGetRssItemsCompleted = null, Action<Exception> onError = null, Action onFinally = null)
{
WebClient webClient = new WebClient();
// register on download complete event
webClient.OpenReadCompleted += delegate(object sender, OpenReadCompletedEventArgs e)
{
try
{
// convert rss result to model
IList<RssItem> rssItems = new List<RssItem>();
Stream stream = e.Result;
XmlReader response = XmlReader.Create(stream);
{
SyndicationFeed feeds = SyndicationFeed.Load(response);
foreach (SyndicationItem f in feeds.Items)
{
RssItem rssItem = new RssItem(f.Title.Text, f.Summary.Text, f.PublishDate.ToString(), f.Links[0].Uri.AbsoluteUri);
rssItems.Add(rssItem);
}
}
// notify completed callback
if (onGetRssItemsCompleted != null)
{
onGetRssItemsCompleted(rssItems);
}
}
finally
{
// notify finally callback
if (onFinally != null)
{
onFinally();
}
}
};
webClient.OpenReadAsync(new Uri(rssFeed));
}
}
}
and finally the RSSItem Class:
namespace WindowsPhone.Helpers
{
public class RssItem
{
public RssItem(string title, string summary, string publishedDate, string url)
{
Title = title;
Summary = summary;
PublishedDate = publishedDate;
Url = url;
// Get plain text from html
PlainSummary = HttpUtility.HtmlDecode(Regex.Replace(summary, "<[^>]+?>", ""));
}
public string Title { get; set; }
public string Summary { get; set; }
public string PublishedDate { get; set; }
public string Url { get; set; }
public string PlainSummary { get; set; }
}
}
Disclaimer: I don't think that binding this many items to a Pivot control is necessarily the right thing to do. Your mileage may vary, but I think a more virtualized solution would be more efficient. For my tests, it seemed to perform OK, but my little voice tells me that there be dragons here...
I recreated your project to the best of my ability and made some enhancements to get it to do what you wanted. Basically, the trick was using a ViewModel that was shared between both the main list page (UserSubmitted.xaml) and the page with the Pivot items on it (PivotPage1.xaml). By setting both page's DataContext property to the same object, we were able to bind both lists to the same source, thus eliminating the need to pass anything around.
In App.xaml.cs:
public static ViewData ViewModel { get; private set; }
private void Application_Launching(object sender, LaunchingEventArgs e)
{
// note: you should properly Tombstone this data to prevent unnecessary network access
ViewModel = new ViewData();
}
Here is how ViewData is defined:
public class ViewData : INotifyPropertyChanged
{
private string _FeedTitle;
private RssItem _SelectedItem = null;
private ObservableCollection<RssItem> _feedItems = new ObservableCollection<RssItem>();
private const string MyRssfeed = "http://feeds.bbci.co.uk/news/rss.xml";
public ViewData()
{
RssService.GetRssItems(
MyRssfeed,
(title, items) =>
{
App.Current.RootVisual.Dispatcher.BeginInvoke(() =>
{
FeedTitle = title;
FeedItems = new ObservableCollection<RssItem>(items);
});
},
(exception) =>
{
MessageBox.Show(exception.Message);
},
null);
}
public ObservableCollection<RssItem> FeedItems
{
get { return _feedItems; }
set
{
if (_feedItems == value)
return;
_feedItems = value;
NotifyPropertyChanged(this, new PropertyChangedEventArgs("FeedItems"));
}
}
public string FeedTitle
{
get { return _FeedTitle; }
set
{
if (_FeedTitle == value)
return;
_FeedTitle = value;
NotifyPropertyChanged(this, new PropertyChangedEventArgs("FeedTitle"));
}
}
public RssItem SelectedItem
{
get { return _SelectedItem; }
set
{
if (_SelectedItem == value)
return;
_SelectedItem = value;
NotifyPropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (PropertyChanged != null)
PropertyChanged(sender, args);
}
}
Once this is established, it's relatively easy to wire up both page's data context properties to App.ViewModel.
Last item was the scrolling and positioning of the selected item when navigating. When you select an item from the list page, the SelectedItem property of the shared ViewModel is bound to the SelectedItem property on the ListBox. After navigation to the details page, we have to find the selected item in the pivot and make it visible:
public PivotPage1()
{
InitializeComponent();
Loaded += (sender, e) =>
{
this.DataContext = App.ViewModel;
var selectedItem = App.ViewModel.SelectedItem;
var pi = ItemPivot.Items.First(p => p == selectedItem);
ItemPivot.SelectedItem = pi;
};
}
Setting the SelectedItem property of the Pivot control scrolls the pivot to the proper item and makes it visible.
The full sample is posted at http://chriskoenig.net/upload/imaged.zip if you want to see it in action.
If I got you correctly, you need to bind listbox in following way:
<ListBox ItemsSource="{Binding items}" SelectedItem="{Binding SelectedFeed, Mode=TwoWay}" />
And then bind Pivot in same way:
<Pivot ItemsSource="{Binding items}" SelectedItem="{Binding SelectedFeed, Mode=TwoWay}" />
Try the following for the pivot (based on Alex's code)
<Pivot ItemsSource="{Binding items}" SelectedItem="{Binding SelectedFeed, Mode=TwoWay}">
<Pivot.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Url}"/>
</DataTemplate>
</Pivot.ItemTemplate>
</Pivot>
It assumes on the pivot page DataContext there is the same object "items" providing access to all the feeditems, and a property SelectedFeed which (as Alex mentioned) supports INotifyPropertyChanged