I use the Xabre BLE library for Xamarin studio. Within my project, I have a bluetooth class, and a ViewModel.
When my app connects to a specified characteristic, I subscribe to a characteristic.ValueUpdated event, from which I update a value in my viewmodel.
In my viewmodel, I have a propertychanged eventhandler, which listens to updates from the bluetooth class.
For some reason however, the setter is not updating my value
Bluetooth class:
public class Bluetooth
{
//code to respectively connect and disconnect
public async void GetValuesFromCharacteristic()
{
CarouselViewModel viewModel = new CarouselViewModel();
Characteristic.ValueUpdated += (s, a) =>
{
viewModel.CurrentValue = Characteristic.Value[7].ToString();
};
await Characteristic.StartUpdatesAsync();
}
}
ViewModel:
public class CarouselViewModel
{
private _currentValue;
public CarouselViewModel()
{
}
public string CurrentValue
{
get
{
return _currentValue;
}
set
{
if(_currentValue != value)
{
_currentValue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(_currentValue));
}
}
}
}
Related
I have an app that needs certain data to be "global" as it needs to be accessible from any page. Because the app is view-first, I decided to use a messenger to pass this global data from viewmodel to viewmodel since I can't pass it in the constructors. I've managed to pass the data to a second viewmodel, but the problem is that I can't figure out how to actually access and use it.
Here's the code first:
Page1 ViewModel
public class Page1ViewModel : ObservableObject
{
// Delegate declaration
public delegate void SendDeviceEventHandler(object source, DeviceEventArgs e);
public event SendDeviceEventHandler SendDeviceEvent;
// Command to Send Data
public RelayCommand SendDataToPage2 { get; set; }
// Ctor
public Page1ViewModel()
{
Device = new DeviceModel("SP-2", "Super Processor");
SendDataToPage2 = new RelayCommand(SendDataToPg2);
}
// Command Method sends data to pg2VM
private void SendDataToPg2(object obj)
{
this.SendDeviceEvent += Page2ViewModel.GetDevice;
OnSendDevice(Device);
}
protected virtual void OnSendDevice(DeviceModel device)
{
if (SendDeviceEvent != null)
SendDeviceEvent(this, new DeviceEventArgs() { DeviceToSend = device });
}
private DeviceModel _device;
public DeviceModel Device
{
get { return _device; }
set
{
_device = value;
RaisePropertyChanged();
}
}
}
// Lets us send type "Device" as arg
public class DeviceEventArgs : EventArgs
{
public DeviceModel DeviceToSend { get; set; }
}
Page2 ViewModel
public class Page2ViewModel : ObservableObject
{
private DeviceModel device {get; set;}
// Ctor
public Page2ViewModel()
{
Device = device; // <- doesn't work
}
// Receive our data here
public static void GetDevice(object source, DeviceEventArgs e)
{
device = new DeviceModel("","");
device = e.DeviceToSend;
Trace.WriteLine(device.Name); // <- Correct Data is here
}
private static DeviceModel _device;
public static DeviceModel Device
{
get { return _device; }
set
{
_device = value;
RaiseStaticPropertyChanged(EventArgs.Empty);
}
}
}
I'd like to be able to use the sent data as if it was created in Pg2VM, just like how we could use the public Device to bind to and modify. The code above shows my last attempt, but I've tried many more including using the property Device directly in GetDevice. That will correctly set the data with a Trace.WriteLine but I can't bind to Device, it's always null.
How can we make that work?
There's something else I'm concerned about which may be the actual issue. To switch views, I use a Click event in a button to call the NavigationService, and the view will then instantiate the viewmodel via setting the DataContext. In that same button I use the Command to activate the messenger to send the data.
If we subscribe to the message the normal way, we have to use something like this:
Page2ViewModel pg2 = new Page2ViewModel();
this.SendDeviceEvent += pg2.GetDevice;
This would then result in 2 instances of Pg2VM and wouldn't work. Instead, I made the subscriber static to avoid this but it feels like bad practice:
// In Page1VM
// Command Method sends data to pg2VM
private void SendDataToPg2(object obj)
{
this.SendDeviceEvent += Page2ViewModel.GetDevice;
OnSendDevice(Device);
}
// In Page2VM
// Receive our data here
public static void GetDevice(object source, DeviceEventArgs e)
{
device = new DeviceModel("","");
device = e.DeviceToSend;
}
Is this bad to do? Is there a better approach to sending data in a view-first app than what I'm doing?
I have a WPF project and a Console one, the point of the WPF is to be the frontend UI and the console application is the logic that does the actual work.
In my backend I have a class with a method that does the work.
public static class BackendClass
{
public static void DoWork(ref string output)
{
//actual work
}
}
From the MVVM frontend my view model starts a task for this method and I want to be able to show status messages on the frontend about it. Things like "Started work.", "Doing so-and-so.", "Finished." and etc.
The code in my view model is:
class ViewModel : INotifyPropertyChanged
{
static string backendOutput;
public string BackendOutput
{
get => backendOutput;
set
{
if (backendOutput != value)
{
backendOutput = value;
OnPropertyChanged("BackendOutput");
}
}
}
public RelayCommand ExecuteCommand { get; private set; }
Task executionTask;
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel()
{
executionTask = new Task(() => BackendClass.DoWork(ref BackendOutput));
}
void OnExecute()
{
executionTask.Start();
ExecuteCommand.RaiseCanExecuteChanged();
}
bool CanExecute()
{
return (executionTask.Status != TaskStatus.Running &&
executionTask.Status != TaskStatus.WaitingToRun);
}
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The "BackendOutput" property is data binded to a text block in the WPF window.
I was thinking of passing the "BackendOutput" property so the "DoWork" method can append its status messages to it, thus raising the changed event, updating the frontend.
However if I try to assign it outside of the constructor I get the error that a property can't be a field initializer or something like that and in this case I get "property can't be passed as a ref parameter".
So how should I alert the frontend of what status messages the back is pumping?
ViewModel communicates with View via PropertyChanged event. So Model also can have an event. ViewModel subscribes to that event, updates property with event data, View gets updated.
Events are kind of protected delegates. So as a first step try to introduce a delegate:
public static void DoWork(Action<string> notifier)
{
notifier("output value");
}
and
executionTask = new Task(() => BackendClass.DoWork(str => { BackendOutput = str; }));
So i'm trying to use my "mediator" class as an in-between for my service and my VM when it comes to a couple of booleans.
In my mediator, we have this:
private bool isAddGroupChecked = false;
public bool IsAddGroupChecked
{
get { return isAddGroupChecked; }
set
{
isAddGroupChecked = value;
NotifyPropertyChanged();
}
}
My service retrieves/changes this value by doing the following (for changing)
ActionMediator.Instance.IsAddGroupChecked = false;
My VM can have that boolean changed through the view and has the following property:
public ActionMediator ActionMediator
{
get { return ActionMediator.Instance; }
}
public bool IsAddGroupChecked
{
get { return ActionMediator.IsAddGroupChecked; }
set
{
ActionMediator.IsAddGroupChecked = value;
NotifyOfPropertyChange(() => IsAddGroupChecked);
}
}
Problem is, when you click the toggle (isAddGroupChecked) the value on the singleton changes to True correctly. However, when my service changes the value (say back to false), the VM isn't being notified of that..... where am I going wrong? I'm doing this so my service and VM are not coupled to eachother by this....
It looks like the PropertyChanged event from your singleton has not been assigned on your VM, and that is why it is not being notified, as your service will change the status on the singleton, not on the VM.
So you have (at least) three options
Hanlde the PropertyChanged event from your singleton on your VM to change your VM state.
public VM()
{
ActionMediator.Instance.PropertyChanged += new PropertyChangedEventHandler(Mediator_PropertyChanged);
}
private void Mediator_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsAddGroupChecked")
this.IsAddGroupChecked = ActionMediator.Instance.IsAddGroupChecked;
}
If you go for this one, it is also a good idea to modify you mediator to avoid looping
private bool isAddGroupChecked = false;
public bool IsAddGroupChecked
{
get { return isAddGroupChecked; }
set
{
if (value != isAddGroupChecked)
{
isAddGroupChecked = value;
NotifyPropertyChanged();
}
}
}
Give an instance of your VM to your service so it can change the state directly (not a good idea though)
Remove the IsAddGroupChecked property from your VM and bind your view directly to ActionMediator.IsAddGroupChecked
Okay, so I've been attempting to learn a bit of MVVM. It's been only three days and have come to a dead end. The application simply retrieves the client's external ip address and updates the new ip address in a label found in my UI. However, rather than just going the old-fashioned way of simply doing,
ipAdd.Content = getNewIp();
and ending the story end there. Rather, I decided to use some MVVM to achieve the following:
Retrieve ip address, then display it to the user in a label
Query the database and update the user's ip address
Rinse, repeat every five minutes.
The reason I chose to go more MVVM is to use UpdateSourceTrigger and UpdateCommand. Essentially, when the ip address gets updated, I want to do something with it (as mentioned above). I've seen in many tutorials where UpdateCommand is used on buttons and UpdateSourceTrigger used on input boxes, but nothing that shows how to do it with labels, yet it seems that it should be tied in to the same concept; it might not be explicity a button that is being clicked, but some back code that executes and performs the same task as a button. For starters, here's what I have so far:
Model
namespace IPdevices
{
class IP : INotifyPropertyChanged
{
private string _add;
public string add
{
get
{
return _add;
}
set
{
_add = value;
OnPropertyChange("add");
}
}
private void OnPropertyChange(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
ViewModel
namespace IPdevices
{
class IPViewModel
{
private IP _ip;
public IPViewModel()
{
UpdateCommand = new IPUpdateCommand(this);
}
public ICommand UpdateCommand
{
get; private set;
}
public bool CanUpdate
{
get
{
return true; // just say yes for now
}
}
public IP IP
{
get
{
return _ip;
}
set
{
_ip = value;
}
}
public void updateClientIp()
{
Console.WriteLine("_updating client ip and query database");
}
}
}
ICommand
namespace IPdevices
{
class IPUpdateCommand : ICommand
{
private IPViewModel _viewModel;
public IPUpdateCommand(IPViewModel vm)
{
_viewModel = vm;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter)
{
return _viewModel.CanUpdate;
}
public void Execute(object parameter)
{
_viewModel.updateClientIp();
}
}
}
MainWindow
public Main()
{
InitializeComponent();
ipAdd.DataContext = new IPViewModel();
}
View
<Label x:Name="ipAdd" Content="{Binding Path=IP.add}"/>
What I'm trying to achieve here can easily be done without going MVVM (perhaps overkill as well) and simply calling a few helper classes to update the user's ip on the db side, but for the sake of getting used to doing things MVVM, I tried incorporating the way I did, though with no luck.
Is there any way to do this, or even better, should it be done this way?
I'm writing an android app that uses the MediaPlayer. I've created a custom IAudioPlayer as a wrapper for the MediaPlayer so that I can eventually extend it to iOS.
public interface IAudioPlayer
{
bool IsPlaying { get; }
void Play(string fileName, int startingPoint);
void Play(string fileName);
void Pause();
void Stop();
int CurrentPosition();
bool HasFile();
void SkipForward(int seconds);
void SkipBackward(int seconds);
void SeekTo(int seconds);
}
Our app is structured using the Mvvm pattern and is using MvvmCross.
On our FragmentViewModel we've got commands such as IPlayCommand, IStopCommand, etc.
public class HomeFragmentViewModel : ViewModelBase
{
public HomeFragmentViewModel(IPlayCommand playCommand,
IStopCommand stopCommand,
ISkipForwardCommand skipForwardCommand,
ISkipBackwardCommand skipBackwardCommand)
{
_playCommand = playCommand;
_stopCommand = stopCommand;
_skipForwardCommand = skipForwardCommand;
_skipBackwardCommand = skipBackwardCommand;
}
private string _playPauseIcon = FontAwesome.icon_play;
public string PlayPauseIcon
{
get { return _playPauseIcon; }
set { _playPauseIcon = value; RaisePropertyChanged(() => PlayPauseIcon); }
}
private IPlayCommand _playCommand;
public IPlayCommand PlayCommand
{
get { return _playCommand; }
set { _playCommand = value; RaisePropertyChanged(() => PlayCommand); }
}
private IStopCommand _stopCommand;
public IStopCommand StopCommand
{
get { return _stopCommand; }
set { _stopCommand = value; RaisePropertyChanged(() => StopCommand); }
}
private ISkipForwardCommand _skipForwardCommand;
public ISkipForwardCommand SkipForwardCommand
{
get { return _skipForwardCommand; }
set { _skipForwardCommand = value; RaisePropertyChanged(() => SkipForwardCommand); }
}
private ISkipBackwardCommand _skipBackwardCommand;
public ISkipBackwardCommand SkipBackwardCommand
{
get { return _skipBackwardCommand; }
set { _skipBackwardCommand = value; RaisePropertyChanged(() => SkipBackwardCommand); }
}
}
On our View we've got buttons that bind to those commands, and all is working as expected.
However
Our view also has a SeekBar that we're going to use for quick scrubbing by the user. The seekbar needs to do two things...
allow the user quickly navigate to the spot that's required (this one "should" be easy
automatically update based on the track's current progress (this one I'm stuck on)
How can I write a notifier that triggers every second, and automatically update the seekbar binding? This notifier would need to live in the PCL with the Command Objects so that it can work cross platform. I'm struggling with getting started on this... I'm not sure where to create it or how to wire it up.
automatically update based on the track's current progress (this one I'm stuck on)
You should be able to do this using a binding to a View property. Here's some pseudo code:
In the ViewModel, add the SeekPosition property:
public double SeekPosition { /* normal INPC get/set */ }
In the View:
add a property and event pair like:
public event EventHandler CurrentPositionChanged;
public double CurrentPosition
{
get { return _mediaPlayer.CurrentPosition; }
set
{
_mediaPlayer.SeekTo(value);
}
}
add a timer to fire the CurrentPositionChanged event on the UI thread. For Android, create this timer in OnResume and destroy it in OnPause.
add a binding in OnCreate:
public override void OnCreate(args)
{
// normal base call and inflate
// ...
var set = this.CreateBindingSet<MyView, MyViewModel>();
set.Bind(this).For(v => v.CurrentPosition).To(vm => vm.SeekPosition);
set.Apply();
}
Note that this approach doesn't use a timer in the PCL. This is because other platforms like iOS and Windows shouldn't need the timer - as they should be able to use progress callbacks/events from the media players on those platforms instead.