LINQ to Twitter setting users account image c# - c#

I am attempting to fetch the users profileImageURl and bind it to an image. I have got the following C# code:
namespace IIVVYTwitter
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
DataContext = new ViewModel(this);
}
}
class ViewModel : INotifyPropertyChanged
{
public User AccountImageUrl { get; set; }
readonly Page page;
public ViewModel(Page page)
{
this.page = page;
LoadAccount = new TwitterCommand<object>(LoadAccountHolder);
}
public TwitterCommand<object> LoadAccount { get; set; }
void LoadAccountHolder(object obj)
{
PinAuthorizer auth =
new PinAuthorizer
{
Credentials = new LocalDataCredentials()
};
if (auth == null || !auth.IsAuthorized)
{
page.Frame.Navigate(typeof(oAuth));
return;
}
var twitterCtx = new TwitterContext(auth);
var accounts =
from acct in twitterCtx.Account
where acct.Type == AccountType.VerifyCredentials
select acct;
Account account = accounts.SingleOrDefault();
LinqToTwitter.User user = account.User;
new User
{
AccountImageUrl = user.ProfileImageUrl
};
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("AccountImageUrl"));
}
}
}
}
Linked with this XAML:
<Page
x:Class="IIVVYTwitter.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:IIVVYTwitter"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Button Command="{Binding LoadAccount}">
<Border>
<Image x:Name="accountPicture" Source="{Binding AccountImageUrl}" />
</Border>
</Button>
</Grid>
</Page>
When I run the command through a button click, the code runs but the source of the Image is not updated.
I'm pretty sure the way I have bound the source data is flawed.
Thanks in advance.

I do not see you setting the DataContext for the page anywhere. Without a properly defined DataContext, the Bindings you are using cannot be resolved to any data object. A simple example would be in the OnNavigatedTo method or the page's constructor, you need to do something like:
this.DataContext = new ViewModel()
or something similar. There are different ways to wire up the data context depending on how you want handle viewmodel creation, lifetime, etc., but hopefully this gets you on the right path. In your code, you could also call page.DataContext = this; in your constructor.
Update: You also need to fix the code when you fire the PropertyChanged event. You have
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ImageUrl"));
}
should be
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("AccountImageUrl"));
}
Notice the change to the string passed for the property name. You were firing an change event for a property that did not exist.
Update #2: You are returning a type of User from AccountImageUrl. You should be returning the actual http url string, not a custom type. If User has a property that hanging off of it that points to the http Url, you could update the Xaml Binding to AccountImageUrl.PropertyThatContainsRealUrl
Update #3:
In your code, you have
new User
{
AccountImageUrl = user.ProfileImageUrl
};
This is setting the AccountImageUrl on the new User object. However, you are binding to the AccountImageUrl of the ViewModel. At some point, you have to set AccountImageURl to the image url string and raise PropertyChanged on AccountImageURL. So, either before or after creating a new User, you should say this.AccountImageUrl = user.ProfileImageUrl. Hopefully I am still following your code and the changes you have made in the comments correctly.

Related

Xamarin.Forms and Prism - How to pass data and navigate to another view?

This's my first question here, so hi everybody.
I'm working on the mobile app in Xamarin.Forms with Prism. I've created ListView where shown data from the database.
When the user clicks in the selected row app should navigate to a new view and pass the selected item from ListView.
<ListView x:Name="DefectsBase"
RowHeight="65"
ItemsSource="{Binding Defects}"
ItemSelected="ShowDetailsEvent"
IsPullToRefreshEnabled="true"
RefreshCommand="{Binding Refresh}"
IsRefreshing="{Binding IsRefreshing}">
Code backend:
async void ShowDetailsEvent(object sender, EventArgs e)
{
var myListView = (ListView)sender;
var myItem = myListView.SelectedItem;
var p = new NavigationParameters();
p.Add("selectedDefect", myItem);
await _navigationService.NavigateAsync("DefectDetailsView", p);
}
Unfortunately, the app doesn't respond to pressing the selected row in ListView.
As I can see you are already using Prism and you have a List page with Items and you want to navigate to some details page based on the selected/taped/chosen item which the user taps in the ListView.
The idea is to move as much code and logic as we can to the view model and keep our code-behind. This is pretty easy to solve using Prism and EventToCommand behaviour.
In the example and answer below, I will show you how to solve this with few lines of code, with a nice code approach.
First of all, I recommend you use EventToCommand behaviour, you can include it with prism xmlns, like this: xmlns:prism="http://prismlibrary.com", later on, you can use it with ListView.
Remove ItemSelected event from your ListView and move the markup about it to the <ListView.Behaviors> part. Here is my code sample for the ListView which binds to some ObserverableCollection of the Car models:
<ListView ItemsSource="{Binding Cars}">
<ListView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<prism:EventToCommandBehavior EventName="ItemTapped"
Command="{Binding SelectedCarCommand}"
EventArgsParameterPath="Item" />
</ListView.Behaviors>
The main part here is <ListView.Behaviors>, where you can see that I am binding to the SelectedCarCommand which will be invoked when the user taps on some of the items from the list. I am using the ItemTapped event for this and passing the current "taped" item from the list as a parameter.
In order to follow this XAML part in my view model of this page, I have declared the DelegateCommand and method which will be called when the command is invoked. The view model part looks like this:
This is my CarListPageViewModel, take a look at DelegateCommand and SelectedCar method.
public class CarListPageViewModel
{
private readonly INavigationService _navigationService;
public ObservableCollection<Car> Cars { get; set; }
public DelegateCommand<Car> SelectedCarCommand { get; private set; }
public CarListPageViewModel(INavigationService navigationService, IDataProvider dataProvider)
{
_navigationService = navigationService;
// Insert test data into collection of Cars
Cars = new ObservableCollection<Car>(dataProvider.GetData());
SelectedCarCommand = new DelegateCommand<Car>(SelectedCar);
}
private async void SelectedCar(Car selectedCar)
{
NavigationParameters navigationParameters = new NavigationParameters
{
{ "selectedCar", selectedCar }
};
await _navigationService.NavigateAsync(nameof(CarDetailsPage), navigationParameters);
}
}
As you can see we have DelegateCommand defined with the type of parameter which will be passed, in my case, this is the Car class, the same class as our items in the ListView.
In the constructor, I did my initialization and defined the method which will be called, that method has a parameter of the type Car.
When the user taps on one of the items in the ListView, SelectedCar (method) will be called and we can pass the data to the next view using NavigationParameters and NavigationService.
In order to retrieve the passed data we can use INavigationAware in the details view model and with the OnNavigatedTo method, access the data which is being passed.
This is my CarDetailsPageViewModel, take a look at OnNavigatedTo method.
public class CarDetailsPageViewModel : BindableBase, INavigationAware
{
private string carTitle;
public string CarTitle
{
get { return carTitle; }
set { SetProperty(ref carTitle, value); }
}
private string photoUrl;
public string PhotoUrl
{
get { return photoUrl; }
set { SetProperty(ref photoUrl, value); }
}
public CarDetailsPageViewModel() { }
public void OnNavigatedTo(INavigationParameters parameters)
{
if (parameters.ContainsKey("selectedCar"))
{
Car car = parameters.GetValue<Car>("selectedCar");
if (car != null)
{
CarTitle = $"{car.Make} {car.Model}";
PhotoUrl = car.PhotoUrl;
}
}
}
public void OnNavigatedFrom(INavigationParameters parameters) { }
}
From this answer and example, you can see:
How to, use EventToCommand behaviour with ListView
Define and use DelegateCommand with passing parameter
How to navigate to another view and pass navigation parameter and
... finally how to access the passed data.
Code and this sample you can find on my GitHub profile here.
Hope this answer was helpful for you!
Wishing you lots of luck with coding! 👋

Bind WebView2 to ViewModel Using Caliburn Micro

I am using the guide located her for reference: https://learn.microsoft.com/en-us/microsoft-edge/webview2/gettingstarted/wpf
Utilizing that guide, I was able to get WebView2 started within my application. Now I am trying to seperate the code into a ViewModel as I will have many more elements on that page. The application as a whole uses Caliburn Micro. I am able to bind everything to the ViewModel except for the WebView2 itself. Wheneer I Select the Go button it states that the WebView is null. I tried manually setting the WebView however that does not work.
BrowserView.xaml:
<Button
x:Name="ButtonGo"
Content="Go"
/>
<TextBox x:Name = "Addressbar"
/>
<wv2:WebView2 x:Name = "WebView"
Source="{Binding WebViewSource}"
/>
BrowserViewModel.cs
private WebView2 _webView;
public WebView2 WebView
{
get
{
return _webView;
}
set
{
_webView = value;
NotifyOfPropertyChange(() => WebView);
}
}
public string WebViewSource { get; set; } = "http://Google.com";
private string _addressbar;
public string Addressbar
{
get
{
return _addressbar;
}
set
{
_addressbar = value;
NotifyOfPropertyChange(() => Addressbar);
}
}
public void ButtonGo()
{
if (WebView != null && WebView.CoreWebView2 != null)
{
WebView.CoreWebView2.Navigate("https://bing.com");
}
}
No matter what I try WebView keeps coming back null and I am not able to change the page.
As aepot commented, removing the Webview property and notifying the change in the source resolved the issue. The code now looks like this for the view:
<Button x:Name="ButtonGo"
Content="Go"/>
<TextBox x:Name = "Addressbar"/>
<wv2:WebView2 x:Name = "WebView"
Source="{Binding WebViewSource}"/>
And this for the ViewModel:
public string Addressbar
{
get
{
return _addressbar;
}
set
{
_addressbar = value;
NotifyOfPropertyChange(() => Addressbar);
}
}
public void ButtonGo()
{
WebViewSource = Addressbar;
NotifyOfPropertyChange(() => WebViewSource);
}
A view model is not supposed to keep a reference to an element like WebView2. This is not MVVM and not how Caliburn.Micro works. A view model defines the source properties.
If you add a TextBlock property (you should not!) to the view model, it will also always be null just like your WebView2 property is, even if you add a TextBlock with a corresponding name in the XAML markup.
I am afraid this doesn't make much sense, both regarding MVVM in general and Caliburn.Micro in particular.

Showing non-list updated value from secondary navigation page on primary page using Xamarin.Forms, binding, and MVVM

This is my first StackOverflow question! I'm trying to get data bound to a ViewModel to appear on one page, allow it to be changed on another page, then appear in its changed form on the first page.
I have a MainPage displaying a label bound to the property ContactName in the ViewModel ContactViewModel. A button on MainPage pushes the DataEntryPage. On the DataEntryPage I have an entry field also bound to the ContactName. A save button pops the page and returns to MainPage.
I want to enter a new value on DataEntryPage, press save, and have the new value display in the label on the MainPage.
If I initialize ContactName, the value appears both on the MainPage and in the entry field of the DataEntryPage. But when I enter a new value and press save, the old value is displayed on MainPage.
I can make the new value display if I add a static instantiation of the ContactViewModel, explicitly set it to the new value in the DataEntryPage save, then force a refresh by overriding the MainPage's OnAppearing and setting the label to that value. With an ObservableCollection and ListView it's super easy, but I can't make it happen with a single property. It seems like I'm missing some basic ability of MVVC.
The stripped down code is--
MainPage.xaml:
<Label Text="{Binding ContactName}">
<Label.BindingContext>
<local:ContactViewModel />
</Label.BindingContext>
</Label>
(a Button which triggers code below)
W
MainPage.xaml.cs:
(Button clicked code)
await Navigation.PushAsync(new DataEntryPage());
DataEntryPage.xaml
<ContentPage.BindingContext>
<local:ContactViewModel />
</ContentPage.BindingContext>
(then in a StackLayout)
<Entry Text={Binding ContactName} />
(Button for save)
DataEntryPage.xaml.cs
(Button clicked code)
await Navigation.PopAsync();
And finally the ContactViewModel.cs
public class ContactViewModel : INotifyPropertyChanged
{
string contactName;
public event PropertyChangedEventHandler PropertyChanged;
public ContactViewModel()
{
contactName = "OriginalName";
}
public static ContactViewModel cvm = new ContactViewModel();
public string ContactName
{
set {
if (contactName!=value)
{
contactName = value;
if (PropertyChanged!=null )
{
PropertyChanged(this, new PropertyChangedEventArgs("ContactName"));
}
}
}
get
{
return contactName;
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Is there a simple solution to this problem? I would prefer to use vanilla Xamarin.forms/C# unless it's absolutely impossible to do without. Thank you for your help!
When you declare the following in XAML
<Control.BindingContext>
<local:ContactViewModel/>
<Control.BindingContext>
a new instance is created. So the DataEntryPage is creating its own instance of ContactViewModel. So the instance that you are updating on the DataEntryPage is different from the instance that you are using to bind the Text property of Label on the MainPage. Using same instance for the pages should resolve your issue

Hybrid MVVM implementation for a PropertyGrid (in DevExpress)

I need your help! Following is basically what I have in my main XAML view :
<Button x:Name="button1" Content= "{Binding Customer1, Mode=TwoWay}" Margin="271,52,103,106" Click="button1_Click" />
The code-behind of the main XAML (Code-behind, since it's not a 100% pure MVVM, and a rather hybrid one) goes like this :
public MainWindow()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
DXDialog d = new DXDialog("Information", DialogButtons.OkCancel,true);
d.Content = new PropertyGrid();
d.SizeToContent = System.Windows.SizeToContent.WidthAndHeight;
d.Owner = this;
d.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterOwner;
var result = d.ShowDialog();
if (result == true)
{
}
}
As you can see, I have a Button whose content is bound to a String property in the ViewModel Class. Upon Clicking the button, I'm opening a DXDialog which contains a PropertyGrid with the Properties of the ViewModel class. Let me show you my ViewModel Class below :
public class MyViewModel : ViewModelBase, INotifyPropertyChanged
{
Customer currentCustomer;
protected string _customer1;
public string Customer1 {
get { return this._customer1; }
set { this.SetProperty(ref this._customer1, value, "Customer1"); }
}
public MyViewModel()
{
//Customers = new ObservableCollection<Customer>();
//Customers.Add(new Customer() { Name = "Name1" });
Customer1 = "ABC";
}
}
In the Dialog I'm being able to edit the value of the property but don't yet know how I can save it in a way that it immediately reflects even on the button of the main View {Reflects everywhere it must be bound to, I mean}. I can see the execution coming to the following line in the main code behind
if (result == true)
{
}
But I don't know how to get the edited values and plug them into the right place.
Basically, My requirement is to have multiple controls (Buttons, in this case) bound to multiple instances of a ViewModel class, and then, upon clicking the buttons, I should be able to edit those specific ViewModel instances inside the PropertyGrid of the DXDialogue, and after clicking "Ok", the changes should reflect on the relevant buttons as well.
-Ron
To display ViewModel's properties in the PropertyGrid, assign the ViewModel to its SelectedObject property,and make sure that the ShowProperties option is set to All.
Changes will be reflected in buttons bound to the ViewModel only of you use one and the same ViewModel instance in the main and the dialog windows.
var grid = new PropertyGrid();
grid.SelectedObject = this.DataContext;
grid.ShowProperties = ShowPropertiesMode.All;
d.Content = grid;

My ViewModel does not provide current variable value

[EDIT] Example reprodoucing this issue Host to AddIn issue
I'm trying to write a host application which is extendable and uses few interfaces. One of them is
public MessageCreator GetMessageCreator()
{
var creator = new AVL2MessageCratorFactory();
return creator.Create(new AVL2DataForMessageCreatingImpl { Imei = VM.Imei });
}
from AddIn and below you can find XAML of AddIn guest application:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:AVL2SimulatorAddView="clr-namespace:AVL2SimulatorAddView" x:Class="AVL2SimulatorAddView.AVL2AddInUI">
<Grid Height="46" Width="344">
<Grid.DataContext>
<AVL2SimulatorAddView:AVL2ViewModel x:Name="VM" />
</Grid.DataContext>
<Label Content="Starting IMEI" Margin="0,0,255,0" Height="29" VerticalAlignment="Top" />
<TextBox Text="{Binding Path=Imei, UpdateSourceTrigger=PropertyChanged }" Margin="120,2,48,0" Width="176" Height="27" VerticalAlignment="Top" />
</Grid>
Imei property from ViewModel
using System.ComponentModel;
namespace AVL2SimulatorAddView
{
public class AVL2ViewModel : INotifyPropertyChanged
{
readonly AVL2Model _avl2Model = new AVL2Model();
public string Imei
{
get { return _avl2Model.Imei; }
set
{
if (value == _avl2Model.Imei) return;
_avl2Model.Imei = value;
OnPropertyChanged("Imei");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
in debug mode I see that property in ViewModel is updated every character is written inside TextBox, but when GetMessageCreator() from Host application is called it seems like new, empty VM and its Imei is returned (I do not see Imei on Host aplication side)
Am I missing some protection in .Net against bypassing bound data? Other "hardcoded" strings are passing well. MVVM and DataContext on Host Application side works well also. I tried different types of UpdateSourceTrigger, but it always seems to work on AddIn side, and it does not provide current date to shared interface.
[EDIT]
Other control bound to Imei, display the changes on-line, when data is entered in textbox
Calling local button set properly the label content
private void button1_Click(object sender, RoutedEventArgs e)
{
label1.Content = VM.Imei;
}
Temporary calling method from Host application
var msgCreator = _tabsMap[(TabItem) tabControl1.SelectedItem].GetMessageCreator();
I'm referring to your demo project. Consider this code in the constructor of LoadGeneratorWPFHostApplication.MainWindow:
// Activate creates instance AAddIn#1 (wpfAddInHostView.wpfAddInContract.wpfAddInView)
foreach (var wpfAddInHostView in addInTokens.Select(addInToken => addInToken.Activate<IWpfAddInHostView>(AddInSecurityLevel.Host)))
{
// AAddin#1.GetAddinUI creates instance AAddIn#2 (tabItem.Content)
var tabItem = new TabItem {Content = wpfAddInHostView.GetAddInUI(), Header = wpfAddInHostView.GetName()};
tabControl1.Items.Add(tabItem);
// adding AAddIn#1 to _tabsMap (but using AAddIn#2 on the UI).
_tabsMap.Add(tabItem, wpfAddInHostView);
}
So what happened here is you use System.AddIn.Hosting.AddInToken.Activate to create an instance of AAddIn (#1). But right after that, you use this AAddIn#1 to create another instance with its method GetAddInUI.
public partial class AAddIn : WPFAddInView
{
public FrameworkElement GetAddInUI()
{
return new AAddIn(); // creates a new instance of itself
}
}
AAddIn#1 is persisted on the contract (WPFAddIn_ContractToViewHostSideAdapter.wpfAddInContract.wpfAddInView) and in _tabsMap, AAddIn#2 is used in the UI (tabItem.Content).
There are different ways to ensure you keep the same instance, I would probably just remove the GetAddInUI method from the WPFAddInView interface (note: always start interface names with an I) as well as the AAddIn class, then return the wpfAddInView directly on the contract:
public class WPFAddIn_ViewToContractAddInSideAdapter : ContractBase, IWPFAddInContract
{
public INativeHandleContract GetAddInUI()
{
FrameworkElement fe = this.wpfAddInView as FrameworkElement; // return instance directly instead of creating new one by wpfAddInView.GetAddInUI();
INativeHandleContract inhc = FrameworkElementAdapters.ViewToContractAdapter(fe);
return inhc;
}
}
Side note: Use the method .GetHashCode() in debugger to differentiate between instances of the same class.

Categories