How to hide/unload a CachedImage in xamarin forms - c#

I am still kind of new to Xamarin forms and now I am using the Ffimagloading library to display a gif through my viewmodel's "DisplayImage" property. But after certain conditions are met, I want to hide/unload the image, so that it is no longer there.
This is the CachedImage in my View:
<ffimage:CachedImage Grid.Column="0"
Source="{Binding DisplayImage}" />
And this is the corresponding part in my ViewModel:
private ImageSource displayImage;
public void DownloadAndAssignImage() {
try
{
var response = await this.DownloadFile(new Uri("..."));
if (response != null)
{
this.DisplayImage = ImageSource.FromStream(() => new MemoryStream(response));
}
}
catch (Exception e)
{
Log.Error(e);
}
}
public void HideImage()
{
// does not work
this.DisplayImage = null;
// does not work too
this.DisplayImage = new byte[0];
}
public ImageSource DisplayImage
{
get => this.displayImage;
set
{
this.displayImage= value;
this.RaisePropertyChanged();
}
}
How can I make it, so that the CachedImage shows nothing again after having assigned an ImageSource to it through "DownloadAndAssignImage()"? Setting the ImageSource to null or to an empty byte[] array does not work. How exactly do I need to modify the "HideImage()" method?
Thanks for your help in advance!

use IsVisible
<ffimage:CachedImage Grid.Column="0" IsVisible="{Binding ImageVisible}"
Source="{Binding DisplayImage}" />
then in your VM (you'll need to implement INotifyPropertyChanged)
ImageVisible = false;

Related

How to bind whole page to BindingContext?

In my application I have a situation where I want to display some object on page and then change this object for different one.
So, let's consider I have MainPage.xaml.cs like this:
...
public Foo Item { get; set; }
public bool SomeCheck {
get {
return Item.Bar != "";
}
}
public MainPage() {
InitializeComponent();
SetItem();
BindingContext = this;
}
private void SetItem() {
Item = DifferentClass.GetNewItem();
}
private void Next_Clicked(object sender, EventArds e){
SetItem();
}
...
and MainPage.xaml like this:
...
<Label Text="{Binding Item.Bar}" IsVisible="{Binding SomeCheck}" />
<Button Text="Next" Clicked="Next_Clicked" />
...
So I want to bind whole page to BindingContext, to achieve this I've set BindingContext = this;. Behaviour which I want is to show Bar property of different objects returned by GetNewItem() and what I get is frozen page. In debugger Item is changing, but on page I have always value which I've set at the first call.
So the question is: can I somehow update BindingContext to show what I want? I tried calling OnPropertyChanged() but it doesn't work for me.
I know I can set up whole object like
BindingContext = { Bar = Item.Bar, SomeCheck = Item.Bar != "" };
and the it works, but of course my real scenario is more complex so I don't want to go this way.
Use OnPropertyChanged:
XAML:
<Label Text="IsVisible" IsVisible="{Binding MyIsVisible}" />
In the viewmodel, in your case in MainPage.xaml.cs:
private bool myIsVisible = true;
public bool MyIsVisible
{
get => myIsVisible;
set
{
myIsVisible = value;
OnPropertyChanged(nameof(MyIsVisible));
}
}

My selectionchanged event is not updating the view, What am I missing?

All fields are marked as TwoWay on databinding, but its obvious I have something wrong. What I have is a page showing a view to add new Devices on one side of the view, and a list of Devices on the other side.. What I'm trying to do is when selecting an item from listview, it will update values within the TextBox for viewing and editing purposes.
The Save option (not shown in Code Below) currently works when I create a new Device, and will refresh the list. however, right now I'm Going back a Frame when complete. What I would like to do is refresh ListView when I click save.
Values from XAML page
<TextBox PlaceholderText="Host Name" Text="{x:Bind ViewModel.HostName, Mode=TwoWay}" Name="hostNameTB" AcceptsReturn="True" />
<TextBox PlaceholderText="Drive Model" Text="{x:Bind ViewModel.DriveModel, Mode=TwoWay}" Name="driveModelTB" />
<TextBox PlaceholderText="Drive SN" Text="{x:Bind ViewModel.DriveSN, Mode=TwoWay}" Name="driveSNTB" AcceptsReturn="True" InputScope="Digits"/>
Code from ViewModel
private Device _ActiveDevice;
private int _HostName;
//All Variables Created for DataBinding to XAML page
//public int HostName { get { return _HostName; } set { _ActiveDevice.HostName = value; } }
public int HostName { get; set; }
public string DriveModel { get; set; }
public string DriveSN { get; set; }
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> suspensionState)
{
Value = (suspensionState.ContainsKey(nameof(Value))) ? suspensionState[nameof(Value)]?.ToString() : parameter?.ToString();
await Task.CompletedTask;
var uri = new Uri("http://localhost:2463/api/Devices");
HttpClient client = new HttpClient();
try
{
var JsonResponse = await client.GetStringAsync(uri);
var devicesResult = JsonConvert.DeserializeObject<List<Device>>(JsonResponse);
Devices = devicesResult;
_ActiveDevice = JsonConvert.DeserializeObject<List<Device>>(JsonResponse)[0];
}
catch
{
MessageDialog dialog = new MessageDialog("Unable to Access WebService at this Time!");
await dialog.ShowAsync();
}
//client.Dispose();
}
public void deviceList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var device = ((sender as ListView).SelectedItem as Device);
_ActiveDevice = device;
HostName = device.HostName;
DriveModel = device.DriveModel;
DriveSN = device.DriveSN;
}
You have to inherit the view model from INotifyPropertyChanged to let the binding know there was an update of the value.
public class MainViewModel: INotifyPropertyChanged
{
public int HostName { get => hostName; set { hostName = value; OnPropertyChanged("HostName"); } }
private int hostName;
...
void OnPropertyChanged(String prop)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Update your binding (and all the others) to this:
<TextBox PlaceholderText="Host Name" Text="{Binding ViewModel.HostName, UpdateSourceTrigger=PropertyChanged}" Name="hostNameTB" AcceptsReturn="True" />
I was way off.. my fix was more of what Sir Rufo proposed.. The XAML was correct, but I needed to set the Get and Set of the property to update the property, and then to make sure selected device was update each property.
private int _HostName;
public int HostName { get { return _HostName; } set { Set(ref _HostName, value); } }
public void deviceList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var device = ((sender as ListView).SelectedItem as Device);
//_ActiveDevice = device;
HostName = device.HostName;
DriveModel = device.DriveModel;
DriveSN = device.DriveSN;

INotifyPropertyChanged doesn't work (UWP)

I need to refresh Image on View
This my ViewPage:
<Image Grid.Row="1"
Grid.Column="1"
x:Name="OriginalImg"
Source="{Binding Orig}"
DataContext="{StaticResource MainViewModel}"/>
I'm using MVVMLibs package. And This is my ViewModel:
public class MainViewModel: ViewModelBase
{
private WriteableBitmap original = new WriteableBitmap(1280,720);
private WriteableBitmap temp = new WriteableBitmap(1280,720);
public WriteableBitmap Orig
{
get { return original; }
set
{
this.original = value;
base.RaisePropertyChanged("Orig");
}
}
public async Task<bool> ApplyEffectAsync(StorageFile file)
{
fileStream = await file.OpenAsync(FileAccessMode.Read);
temp.SetSource(fileStream);
Orig = temp;
}
}
But Image on my Page not displayed. What's my problem?
Problem is you are not really instantiating a new WriteableBitmap, just changing its source. So while it might work the first time, it certainly won't work after because the Dependency Property Manager won't know that your image changed unless its instace changes.
Try creating your temp WriteableBitmap in the ApplyEffectAsync method.

WPF Databinding, no clue why it isn't working

I have successfully bound window items to view models before using wpf data binding, almost, the exact same way as I'm doing here.
I have a GUI with the XAML for my TextBlock binding to change the colour and text with the system state;
<TextBlock
HorizontalAlignment="Left" Margin="200,359,0,0" TextWrapping="Wrap"
Text="{Binding Path=StateText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Top" Width="565" Height="84"
Background="{Binding Path=StateColour, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
I set the datacontext to my view model in my xaml.cs;
MobilityWelcomeViewModel mobilityWelcomeViewModel = new mobilityWelcomeViewModel();
public MobilityWelcome()
{
InitializeComponent();
this.DataContext = this.mobilityWelcomeViewModel;
}
I have this constructor which writes to my data model via the specified adapter;
public class MobilityWelcomeViewModel
{
private bool State;
private string _Text;
private Brush _StateColour;
BackgroundWorker StateWorker = new BackgroundWorker();
}
public ShellEMobilityWelcomeViewModel()
{
this._ANMStateColour = Brushes.White;
this.ANMStateWorker.DoWork += this.ANMStateWorker_DoWork;
this.ANMStateWorker.RunWorkerCompleted += this.ANMStateWorker_RunWorkerCompleted;
this.ANMStateWorker.RunWorkerAsync();
this._ANMText = "Loading ANM State";
IApplicationPointAdapter testWrite = AdapterFactory.Instance.GetApplicationPointAdapter();
testWrite.WriteBinary("HMI.EV.SITE1.STATUS.CONTACTBREAKEROPEN", false);
}
In my view model I have the properties;
public Brush StateColour
{
get { return this._StateColour; }
set { this._StateColour = value; }
}
public string StateText
{
get { return this._Text; }
set { }
}
I have background workers which I can see change these values in debug.
I'm really stumped here. The whole binding thing seems pretty simple at surface so, from my fairly new and probably naive, knowledge of it I can't see what I've done wrong.
Thanks in advance. (also i've changed the variable names to disguise my project so if there is a spelling disparoty between like objects or likewise just ignore it)
I think you are setting the datacontext but not initialising your ViewModel in the right place.
Just to double check you can use tools like Snoop to see what is going wrong.
You should be initialising your ViewModel in the contructor
like below.
public MobilityWelcome()
{
InitializeComponent();
mobilityWelcomeViewModel = new mobilityWelcomeViewModel();
this.DataContext = this.mobilityWelcomeViewModel;
}
Also make sure you are implementing INotificationPropertyChanged.
Your property setters should be like below
public Brush StateColour
{
get { return this._StateColour; }
set { this._StateColour = value;
OnPropertyChanged("StateColour");
}
}

WPF databinding not working via XAML (Only via code)

When trying to bind a ListView to an ObservableCollection via XAML, the ListView is not updated and is initially loaded with blank values.
Via XAML
History.xaml.cs
DataContext = this;
History.xaml:
<ListView x:Name="lvHistory" ItemsSource="{Binding Source=history}" BorderThickness="0" Margin="0,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="2" util:GridViewSort.AutoSort="True" SizeChanged="lvHistory_SizeChanged">
Via CODE
When doing the binding via code, the bindings work correctly.
History.xaml
<ListView x:Name="lvHistory" BorderThickness="0" Margin="0,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="2" util:GridViewSort.AutoSort="True" SizeChanged="lvHistory_SizeChanged">
History.xaml.cs
DataContext = this;
lvHistory.ItemsSource = history;
By simply adding the ItemsSource via code and removing it in XAML, the code works properly. What am I missing? How do I create the bindings via pure XAML?
history:
public ObservableCollection<LocateElement> history { get; private set; }
Code for updating the list:
public void Update()
{
if (updater.IsBusy) updatePending = true;
else
{
searchValue = txtSearch.Text.Trim();
updatePending = false;
updater.RunWorkerAsync();
}
}
private void updateContent(object sender, DoWorkEventArgs e)
{
try
{
Globals.Variables.logger.Info("Locate History: Updating");
using (var db = new Data.DataManager())
{
var history = db.LocateHistory.Where(o => o.ReceivedBy == Globals.Variables.loginDetails.UserID);
e.Result = filterResults(history);
}
}
catch (Exception er)
{
Globals.Variables.logger.Error(er);
}
}
private void updateFinished(object sender, RunWorkerCompletedEventArgs e)
{
List<LocateElement> r = (List<LocateElement>)e.Result;
history.Clear();
foreach (LocateElement l in r)
{
history.Add(l);
}
if (updatePending) Update();
//else Wpf.Util.GridViewSort.ReapplySort(lvHistory);
}
private List<LocateElement> filterResults(IQueryable<LocateElement> list)
{
List<LocateElement> history = new List<LocateElement>();
foreach (LocateElement l in list)
{
if (searchValue != "")
{
// Use the parameters to filter the results.
Regex reg = new Regex(WildcardToRegex(searchValue));
if (reg.IsMatch(l.Serial) || reg.IsMatch(l.Asset) || reg.IsMatch(l.DeviceType) || reg.IsMatch(l.Company) || (l.ReceivedFrom != null && reg.IsMatch(l.ReceivedFrom.Name)) || (l.ReceivedTo != null && reg.IsMatch(l.ReceivedTo.Name)) || reg.IsMatch(l.Row) || reg.IsMatch(l.Shelf) || reg.IsMatch(l.Bin) || reg.IsMatch(l.DateReceived.ToString()))
{
history.Add(l);
}
}
else
{
history.Add(l);
}
}
return history;
}
When you assign data to your history collection you need to make sure you raise the property changed event.
For example:
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<LocateElement> _history;
public ObservableCollection<LocateElement> history
{
get { return _history; }
set
{
if (_history != value)
{
_history = value;
RaisePropertyChanged("history");
}
}
}
public MyViewModel()
{
_history = new ObservableCollection<LocateElement>();
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The Source property of a Binding doesn't mean what you think it means. Use Path instead or let it assume you're talking about Path (default). This should do it.
<ListView ItemsSource="{Binding history}" ...>
Additionally, if you're setting the history property outside your constructor it needs to notify of property changed. IF you're only setting it in your constructor you won't need to but you might want to make it backed by a readonly field instead of an auto getter/setter. (TrueEddie's solution describes this problem and supplies the solution for being able to swap out the actual variable).

Categories