Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 3 years ago.
Improve this question
I need a ListView Renderer for Chat like whatsapp.
when if the new Message comes its automatically scroll down.
Please let me know if have a sample for this.
thanks
You don't need a custom renderer, you can just use a ListView and add some logic to do the scroll for you.
The View.xaml file:
<!-- Previous Implementation -->
<ListView x:Name="MessagesListView"
Grid.Row="0"
BackgroundColor="Transparent"
HasUnevenRows="True"
ItemTemplate="{StaticResource MessageTemplateSelector}"
ItemsSource="{Binding Messages}"
SelectionMode="None"
SeparatorVisibility="None" />
<!-- Remaining Implementation -->
The x:Name attribute is the important part, you're going to use that name in the code behind.
And now the View.xaml.cs file:
// Previous Implmentation
/// <summary>
/// Override of OnAppearing method. Fires as page is appearing.
/// Good place to set up event handlers.
/// </summary>
protected override void OnAppearing()
{
base.OnAppearing();
((INotifyCollectionChanged)MessagesListView.ItemsSource).CollectionChanged += OnListViewCollectionChanged;
}
/// <summary>
/// Override of OnDisappearing method. Fires as page is disappearing.
/// Good place to tear down event handlers.
/// </summary>
protected override void OnDisappearing()
{
base.OnDisappearing();
((INotifyCollectionChanged)MessagesListView.ItemsSource).CollectionChanged -= OnListViewCollectionChanged;
}
/// <summary>
/// Scrolls a the messages listview to the last item whenever
/// a new message is added to the collection.
/// </summary>
private void OnListViewCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
var myList = ((IEnumerable<Message>)MessagesListView.ItemsSource).ToList();
// Must be ran on main thread or Android chokes.
Device.BeginInvokeOnMainThread(async () =>
{
// For some reason Android requires a small delay or the scroll never happens.
await Task.Delay(50);
MessagesListView.ScrollTo(myList.Last(), ScrollToPosition.End, false);
});
}
// Remaining Implementation
Basically you're going to set an event to fire whenever the ListView's ItemSource changes. In that event, you're going to scroll to the end of the list.
You can achieve your requirement by scrolling the ListView to its last index after new item added into collection. To scroll the listview you can call LayoutManager.ScrollToRowIndex method by passing itemIndex.
private void InitializeSendCommand()
{
SendIcon = ImageSource.FromResource("SfListViewSample.Images.SendIcon.png", assembly);
NewText = "";
SendCommand = new Command(() =>
{
if (!string.IsNullOrWhiteSpace(NewText))
{
MessageInfo.Add(new MessageInfo
{
OutgoingMessageIndicator = ImageSource.FromResource("SfListViewSample.Images.OutgoingIndicatorImage.png", assembly),
Text = NewText,
TemplateType = TemplateType.OutGoingText,
DateTime = string.Format("{0:HH:mm}", DateTime.Now)
});
(ListView.LayoutManager asLinearLayout).ScrollToRowIndex(MessageInfo.Count - 1, Syncfusion.ListView.XForms.ScrollToPosition.Start);
}
NewText = null;
});
}
We have attached sample for your reference,
Sample Link:[http://www.syncfusion.com/downloads/support/directtrac/237037/ze/Sample2053309646][1]
Hope this helps.
Syncfusion Support Team
You need not a Custom Renderer for chat as a simple ListView will suffice.
Basically, you will bind the ItemsSource property to an ObservableCollection so when new messages are added, it will automatically appear on the listview.
Also, you might want to use Infinite Scrolling technique if there are significant number of historical chat messages that you believe user doesn't need/have to view them at once e.g. https://www.youtube.com/watch?v=DG5Asglf0vU
To scroll to the last message:
Device.BeginInvokeOnMainThread (() => {
Listviewname.scrollto(items[count-1], scrolltoposition.end, false)
});
});
Related
I have a form that has a dynamic amount of datagrids that are brought in programmatically each one on a new tabpage.
My problem is that I need to change the Header of each column. I have tried doing it through a method
DataGridForSupplier.Columns[0].Header = "123";
but that keeps crashing with an error:
Index was out of range. Must be non-negative and less than the size of the collection
Turns out the problem is that the grid wasn't finished loading. So after waiting for all tabpage to load and add data to all the grids , even then the code
DataGridForSupplier.Columns[0].Header = "123";
would still crash. If the tabs are left to load on their own with no header tampering then the datagrid shows fine.
I would just LOVE to do this in XAML problem is that seeing that I don't know how many grids will load at run time I tried doing this at the back. So I'm open to any solution at this point. I tried finding a solution that would incorporate something that would 'theme' all the datagrids. Luckily all the datagrids headers will repeat across all tabs. So header 1 on tabpage 1 - 10 will be the same. Header 2 on tabpage 1 - 10 will be the same
Something like
<DataGridTemplateColumn.Header>
<TextBlock Text="{Binding DataContext.HeaderNameText, RelativeSource=>> RelativeSource AncestorType={x:Type DataGrid}}}" />
</DataGridTemplateColumn.Header>
but this needs to repeat for every Grid. This seems to escape me at the moment.
Any help would be welcome.
A rather lengthy answer, but this solution does not require any additional libraries, 3rd party tools, etc. You can expand it as you want later such as for adding hooks to mouse-move/over/drag/drop/focus, etc. First the premise on subclassing which I found out early in my learning WPF. You can not subclass a xaml file, but can by a .cs code file. In this case, I subclassed the DataGrid to MyDataGrid. Next, I created an interface for a known control type to ensure contact of given functions/methods/properties. I have stripped this version down to cover just what you need to get.
The interface below is just to expose any class using this interface MUST HAVE A METHOD called MyDataGridItemsChanged and expects a parameter of MyDataGrid.. easy enough
public interface IMyDataGridSource
{
void MyDataGridItemsChanged(MyDataGrid mdg);
}
Now, declaring in-code a MyDataGrid derived from DataGrid. In this class, I am adding a private property of type IMyDataGridSource to grab at run-time after datagrids are built and bound.
public class MyDataGrid : DataGrid
{
// place-holder to keep if so needed to expand later
IMyDataGridSource boundToObject;
public MyDataGrid()
{
// Force this class to trigger itself after the control is completely loaded,
// bound to whatever control and is ready to go
Loaded += MyDataGrid_Loaded;
}
private void MyDataGrid_Loaded(object sender, RoutedEventArgs e)
{
// when the datacontext binding is assigned or updated, see if it is based on
// the IMyDataGridSource object. If so, try to type-cast it and save into the private property
// in case you want to add other hooks to it directly, such as mouseClick, grid row changed, etc...
boundToObject = DataContext as IMyDataGridSource;
}
// OVERRIDE the DataGrid base class when items changed and the ItemsSource
// list/binding has been updated with a new set of records
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
// do whatever default behavior
base.OnItemsChanged(e);
// if the list is NOT bound to the data context of the IMyDataGridSource, get out
if (boundToObject == null)
return;
// the bound data context IS of expected type... call method to rebuild column headers
// since the "boundToObject" is known to be of IMyDataGridSource,
// we KNOW it has the method... Call it and pass this (MyDataGrid) to it
boundToObject.MyDataGridItemsChanged(this);
}
}
Next into your form where you put the data grid. You will need to add an "xmlns" reference to your project so you can add a "MyDataGrid" instead of just "DataGrid". In my case, my application is called "StackHelp" as this is where I do a variety of tests from other answers offered. The "xmlns:myApp" is just making an ALIAS "myApp" to the designer to it has access to the classes within my application. Then, I can add
<Window x:Class="StackHelp.MyMainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myApp="clr-namespace:StackHelp"
Title="Main Window" Height="700" Width="900">
<StackPanel>
<!-- adding button to the main window to show forced updated list only -->
<Button Content="Refresh Data" Width="100"
HorizontalAlignment="Left" Click="Button_Click" />
<myApp:MyDataGrid
ItemsSource="{Binding ItemsCollection, NotifyOnSourceUpdated=True}"
AutoGenerateColumns="True" />
</StackPanel>
</Window>
Now, into the MyMainWindow.cs code-behind
namespace StackHelp
{
public partial class MyMainWindow : Window
{
// you would have your own view model that all bindings really go to
MyViewModel VM;
public MyMainWindow()
{
// Create instance of the view model and set the window binding
// to this public object's DataContext
VM = new MyViewModel();
DataContext = VM;
// Now, draw the window and controls
InitializeComponent();
}
// for the form button, just to force a refresh of the data.
// you would obviously have your own method of querying data and refreshing.
// I am not obviously doing that, but you have your own way to do it.
private void Button_Click(object sender, RoutedEventArgs e)
{
// call my viewmodel object to refresh the data from whatever
// data origin .. sql, text, import, whatever
VM.Button_Refresh();
}
}
}
Finally to my sample ViewModel which incorporates the IMyDataGridSource
public class MyViewModel : IMyDataGridSource, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
public ObservableCollection<OneItem> ItemsCollection { get; set; }
= new ObservableCollection<OneItem>();
public void Button_Refresh()
{
ItemsCollection = new ObservableCollection<OneItem>
{
new OneItem{ DayName = "Sunday", DayOfWeek = 0},
new OneItem{ DayName = "Monday", DayOfWeek = 1},
new OneItem{ DayName = "Tuesday", DayOfWeek = 2},
new OneItem{ DayName = "Wednesday", DayOfWeek = 3},
new OneItem{ DayName = "Thursday", DayOfWeek = 4},
new OneItem{ DayName = "Friday", DayOfWeek = 5 },
new OneItem{ DayName = "Saturday", DayOfWeek = 6 }
};
RaisePropertyChanged("ItemsCollection");
}
// THIS is the magic hook exposed that will allow you to rebuild your
// grid column headers
public void MyDataGridItemsChanged(MyDataGrid mdg)
{
// if null or no column count, get out.
// column count will get set to zero if no previously set grid
// OR when the items grid is cleared out. don't crash if no columns
if (mdg == null)
return;
mdg.Columns[0].Header = "123";
}
}
Now, taking this a step further. I don't know how you manage your view models and you may have multiple grids in your forms and such. You could create the above MyViewModel class as a smaller subset such as MyDataGridManager class. So each datagrid is bound to its own MyDataGridManager instance. It has its own querying / populating list for the grid, handling its own rebuild column headers, mouse clicks (if you wanted to expand), record change selected, etc.
Hope this helps you some. Again, this does not require any other 3rd party libraries and you can extend as you need. I have personally done this and more to the data grid and several other controls for certain specific pattern handling.
(See Solution below)
Struggling learning MVVMLight and WPF. Happy to get page navigation with a frame with 3 buttons and also get the CanExecute RelayCommand functionality working for the buttons.
However, when I change pages with the Back and Forward button from the frame, the frame navigation buttons seem to not raise the propertychanged for the Frame Source Property FrameURI in the viewmodel. In the RelayCommand execute I'm looking at the FrameURI property to determine button command can execute. I'm thinking that I need to raisepropertychange of FrameURI property in the viewmodel after a Frame FWD or BACK button press. It looks like I can do that in a Navigated or LoadCompleted EVENT from the NavigationService Class. Is this the best way to go about this? Maybe there's an easier way?
PROPER BEHAVIOR HERE:
NOT WORKING CORRECTLY HERE when the frame BACK button is pressed. The page changes (good), but the button Middle canexecute didn't update correctly. See property and relay commands further below.
MVVMINPCPROPERTY snippet
public const string FrameUriPropertyName = "FrameUri";
private Uri _frameUri;
/// <summary>
/// Sets and gets the FrameUri property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public Uri FrameUri
{
get
{
return _frameUri;
}
set
{
Set(FrameUriPropertyName, ref _frameUri, value);
}
}
XAML for Frame
<Frame x:Name="MainFrameDS" Source="{Binding FrameUri}" HorizontalAlignment="Left" Height="211" Margin="109,88,0,0" VerticalAlignment="Top" Width="258"/>
RelayCommand example
private RelayCommand _changeToLastPage;
/// <summary>
/// Gets the ChangeToLastPage.
/// </summary>
public RelayCommand ChangeToLastPage
{
get
{
return _changeToLastPage
?? (_changeToLastPage = new RelayCommand(
() =>
{
FrameUri = ViewModelLocator.LastPageUri;
},
() => FrameUri != ViewModelLocator.LastPageUri));
}
}
I have the source code on github and using Win7 and VS2017 Community. I also have other issues w/ the program and opened issues on github. Any help is appreciated.
My goal is to have a few basic MVVMLight program 'templates' that I can pull from and share them w/ other learners like me... Thanks.
Solution
Add Mode=twoway to the binding per #steve-teece.
Did some DEBUG.writeline (Break point suggestion per #Michael-Randall) to the output window and found out that the URI for the page was different depending on if it referred back to the viewmodellocater.IntroPageUri or called by the frame FWD or BACK button.
The two URI results were:
/IntroPage.xaml OR IntroPage.xaml
My assumption was that the 'non-fore slash' version didn't equal the version with a '/'.
I'm not sure of the best way to solve this so I converted the URI's to strings and compared them w/ the string Contains method in a boolean method.
I replaced the follow line:
FrameUri != ViewModelLocator.IntroPageUri
It was replaced with a method call:
CheckUri(FrameUri, ViewModelLocator.IntroPageUri)
And, the CheckUri method:
private Boolean CheckUri(Uri _frameUriToCheck, Uri _vmUri)
{
string StringUriToCheck = _frameUriToCheck.ToString();
string StringUriVM = _vmUri.ToString();
System.Diagnostics.Debug.WriteLine(StringUriToCheck, "StringUriToCheck");
System.Diagnostics.Debug.WriteLine(StringUriVM, "StringUriVM");
if (StringUriVM.Contains(StringUriToCheck))
{ return false; }
else
{ return true; }
}
That worked! If someone has a better way to solve it, I'm all ears.
Thanks all for the feedback!
try changing the XAML to
<Frame x:Name="MainFrameDS" Source="{Binding FrameUri, Mode=TwoWay}" HorizontalAlignment="Left" Height="211" Margin="109,88,0,0" VerticalAlignment="Top" Width="258"/>
Firstly, make sure you are referencing
GalaSoft.MvvmLight.CommandWpf;
and not
using GalaSoft.MvvmLight.Command;
If your CanExcute is not getting evaluated you might need to manually raise the change via RaiseCanExecuteChanged
public Uri FrameUri
{
get
{
return _frameUri;
}
set
{
if(Set(FrameUriPropertyName, ref _frameUri, value))
{
// assuming this is the command you are having trouble with
// this will force the command to reevaluate CanExecute
ChangeToLastPage.RaiseCanExecuteChanged();
}
}
}
Update
Omg i didnt even notice this
public const string FrameUriPropertyName = "FrameUri";
you need to at least be setting this properly
Set(() => FrameUri, ref _frameUri, value);
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
Display data from API, into WPF application.
Hello, Im creating an application for a questionnaire. It calls the data from the API and i would like to know what is the best way to display the information.
The information displayed will have to be "questionnaire type" and will be attempted by users and saved into database with the selected values.
Thanks
I already tried through textboxes that are dynamically created from a List, but setting the location of the textboxes tend to be full of errors
Please have a look at StackPanel
It stacks its child elements below or beside each other, dependening on its orientation.
With a stack panel you can place multiple elements atop of each other, see the sample from the link provided
<StackPanel>
<TextBlock Margin="10" FontSize="20">How do you like your coffee?</TextBlock>
<Button Margin="10">Black</Button>
<Button Margin="10">With milk</Button>
<Button Margin="10">Latte machiato</Button>
<Button Margin="10">Chappuchino</Button>
</StackPanel>
this will result in the following layout
You can create these dynamically. Take the following template
<StackLayout x:Name="QuestionStack">
<TextBlock Margin="10" FontSize="20" x:Name="QuestionTextBlock" />
</StackLayout>
and in your code behind
void DisplayQuestion(Question question)
{
QuestionTextBlock.Text = question.QuestionText;
foreach(var answer in question.Answers)
{
AddAnswerButton(answer);
}
}
private void AddAnswer(Answer answer)
{
QuestionStack.Children.Add(CreateButtonForAnswer(answer));
}
private Button CreateButtonForAnswer(Answer answer)
{
var button new Button()
{
Content = answer.Text,
Margin = 10
}
button.Click += (sender, eventArgs) =>
{
// handle button click
};
return button;
}
Please note: This is only one possibility. Without knowing more about your requirements it's hard to tell what you need exactly.
Edit:
Since you asked: You could for example create a custom control to display one question (I've replaced the Button with CheckBox and for sake of simplicity I've omitted the XAML, but it is not too hard to achieve the same results with XAML)
class QuestionControl : ContentControl
{
private Question question;
private StackLayout QuestionStackLayout { get; }
public QuestionControl()
{
QuestionStackLayout = new StackLayout();
QuestionTextBlock = new TextBlock()
{
Margin = 10,
FontSize = 20
};
QuestionStackLayout.Children.Add(QuestionTextBlock);
}
public Question Question
{
get
{
return question;
}
set
{
question = value;
DisplayQuestion();
}
}
private void DisplayQuestion()
{
QuestionTextBlock.Text = question.QuestionText;
foreach(var answer in question.Answers)
{
AddAnswerButton(answer);
}
}
private void AddAnswer(Answer answer)
{
QuestionStack.Children.Add(CreateButtonForAnswer(answer));
}
private CheckBox CreateCheckBoxForAnswer(Answer answer)
{
var checkBox new CheckBox()
{
Content = answer.Text,
Margin = 10
}
checkBox.Checked += (sender, eventArgs) =>
{
answer.IsSelected = (sender as CheckBox).IsChecked;
};
return checkBox;
}
}
You can now stack instances of QuestionControl. Since the Answer-objects are updated by the CheckBox.Click event you can simply access QuestionControl.Question to get which answers are selected. You might think about deep copying the Question when setting QuestionControl.Question instead of just setting the reference, to avoid side effects.
Im developing an windows phone 8.1 app. I have used shared app bar across the pages. At some point of time, I need to change the label or the icon of the global app bar. Is this possible?
your thoughts will be helpful.
BottomAppBar at WP8.1 is CommandBar, there you will find PrimaryCommands property, in which you probably have AppBarButtons. If you want to change for example a Label (Icon or anything else), you should be able to do it like this:
// change the label of the first button
((BottomAppBar as CommandBar).PrimaryCommands[0] as AppBarButton).Label = "New Label";
If you want to change AppBarButton's parameters often, to make it easier, you can write a simple extension method:
/// <summary>
/// Get AppBarButton of AppBar - extension method
/// </summary>
/// <param name="index">index of target AppBarButton</param>
/// <returns>AppBarButton of desired index</returns>
public static AppBarButton PButton(this AppBar appBar, int index) { return (appBar as CommandBar).PrimaryCommands[index] as AppBarButton; }
Of course it's the same as above, but makes little easier to use:
BottomAppBar.PButton(0).Label = "New label";
If you store your app bar in Resources than you can access it through the following code:
var commandBar = Application.Current.Resources["YouResourceKeyForAppBar"] as AppBar;
Now you have a reference to it and you can modify items inside it. If you implemented SharedApp in another way, please edit your question and tell more information about it.
Perhaps I can suggest an alternate solution thanks to the 8.1 libraries in the Windows.UI.ViewManagement namespace namely the StatusBar and the ApplicationView's Title
It won't help out this situation, but might make things simpler for another who is looking at this post.
var titleName = "TITLE";
var statusBar = Windows.UI.ViewManagement.StatusBar.GetForCurrentView();
statusBar.ProgressIndicator.Text = titleName;
var applicationView = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView();
applicationView.Title = titleName;
Then to show and hide, control with:
await statusBar.ProgressIndicator.ShowAsync();
await statusBar.ProgressIndicator.HideAsync();
Unfortunately, the only thing you'll be able to set up here is Title. I don't think you can set a custom Style to the StatusBar.
public MainPage()
{
this.InitializeComponent();
Loaded += MainPage_Loaded;
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
CommandBar bottomCommandBar = this.BottomAppBar as CommandBar;
AppBarButton appbarButton_0 = bottomCommandBar.PrimaryCommands[0] as AppBarButton;
appbarButton_0.Label = "settings";
appbarButton_0.Icon = new SymbolIcon(Symbol.Setting);
}
I am building a small wpf app in C#. When a button gets clicked a third
party dll function constructs a tree like object. This object is bound
to a treeview. This works fine but takes a bit of time to load. As the
dll function constructs the object it prints progress info to the
console. I want to redirect this into a TextBlock so that the user
gets to see the progress messages.
My window ctor looks like this:
InitializeComponent();
StringRedir s = new StringRedir(ref ProgressTextBlock);
Console.SetOut(s);
Console.SetError(s);
this.DataContext = s;
xaml:
<TextBlock Text="{Binding Path=Text}" Width="244"
x:Name="ProgressTextBlock" TextWrapping="Wrap" />
<TreeView >...</TreeView>
The StringRedir class is shown below. The problem is the TextBlock for
some reason does not get updated with the messages until the TreeView
gets loaded. Stepping through I see the Text property being updated
but the TextBlock is not getting refreshed. I added a MessageBox.Show
() at the point where Text gets updated and this seems to cause the
window to refresh each time and I am able to see each message. So I
guess I need some way to explicitly refresh the screen...but this
doesnt make sense I thought the databinding would cause a visual
refresh when the property changed. What am I missing here? How do I
get it to refresh? Any advice is appreciated!
public class StringRedir : StringWriter , INotifyPropertyChanged
{
private string text;
private TextBlock local;
public string Text {
get{ return text;}
set{
text = text + value;
OnPropertyChanged("Text");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public StringRedir(ref TextBlock t)
{
local = t;
Text = "";
}
public override void WriteLine(string x)
{
Text = x +"\n";
//MessageBox.Show("hello");
}
}
You haven't included the code that is loading the data for the TreeView, but I'm guessing it's being done on the UI thread. If so, this will block any UI updates (including changes to the TextBlock) until it has completed.
So after doing some reading on the WPF threading model ( http://msdn.microsoft.com/en-us/library/ms741870.aspx ) I finally got it to refresh by calling Dispatcher Invoke() with Dispatch priority set to Render. As Kent suggested above UI updates in the dispatcher queue were probably low priority. I ended up doing something like this.
XAML
<TextBox VerticalScrollBarVisibility="Auto"
Text="{Binding Path=Text, NotifyOnTargetUpdated=True}"
x:Name="test" TextWrapping="Wrap" AcceptsReturn="True"
TargetUpdated="test_TargetUpdated"/>
C# target updated handler code
private void test_TargetUpdated(object sender, DataTransferEventArgs e)
{
TextBox t = sender as TextBox;
t.ScrollToEnd();
t.Dispatcher.Invoke(new EmptyDelegate(() => { }), System.Windows.Threading.DispatcherPriority.Render);
}
Note: Earlier I was using a TextBlock but I changed to a TextBox as it comes with scrolling
I still feel uneasy about the whole flow though. Is there a better way to do this?
Thanks to Matt and Kent for their comments. If I had points would mark their answers as helpful.
I believe the problem is in the constructor of your StringRedir class. You're passing in ProgessTextBlock, and you're doing this to it:
local.Text = "";
This is effectively overwriting the previously set value for ProgressTextBlock.Text, which was this:
{Binding Text}
See what I mean? By explicitly setting a value to the TextBlock's Text property, you've cancelled the binding.
If I'm reading right, it looks like the idea of passing a TextBlock into the StringRedir's ctor is a hangover from before you tried binding directly. I'd ditch that and stick with the binding idea as it's more in the "spirit" of WPF.