Hey I'm new to Xamarin and I'm hoping you guys could help me. Since there isn't a default folder-picker in xamarin, I want to implement it myself. The problem is that UWP as well as Android throw me this exception:
System.UnauthorizedAccessException
HResult=0x80070005
Nachricht = Access to the path 'C:\Users\imtt\AppData\Local\Packages\3ef1aa30-7ffe-4ece-84c7-d2eaf1f8634b_wvdsmkc2tee92\LocalState\Test\199.jpg' is denied.
Quelle = System.IO.FileSystem
Stapelüberwachung:
bei System.IO.FileSystem.DeleteFile(String fullPath)
bei System.IO.File.Delete(String path)
bei MinimalReproducibleExample.ViewModel.DeleteFiles() in C:\Users\imtt\source\repos\MinimalReproducibleExample\MinimalReproducibleExample\MinimalReproducibleExample\ViewModel.cs: Zeile107
bei Xamarin.Forms.Command.<>c__DisplayClass4_0.<.ctor>b__0(Object o)
bei Xamarin.Forms.Command.Execute(Object parameter)
bei Xamarin.Forms.ButtonElement.ElementClicked(VisualElement visualElement, IButtonElement ButtonElementManager)
bei Xamarin.Forms.Button.SendClicked()
bei Xamarin.Forms.Platform.UWP.ButtonRenderer.OnButtonClick(Object sender, RoutedEventArgs e)
Here's the xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MinimalReproducibleExample.MainPage">
<StackLayout>
<Button Text="Add Image" Command="{Binding AddImage}"/>
<Button Text="Delete Images" Command="{Binding DeleteImages}"/>
<Image Source="{Binding CreatedImage}"/>
</StackLayout>
Here's the code-behind:
using Xamarin.Forms;
namespace MinimalReproducibleExample
{
public partial class MainPage : ContentPage
{
public MainPage()
{
BindingContext = new ViewModel();
InitializeComponent();
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace MinimalReproducibleExample
{
public class ViewModel : INotifyPropertyChanged
{
private ImageSource image;
private string fileFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Test");
public ICommand AddImage { get; }
public ICommand DeleteImages { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public ViewModel()
{
AddImage = new Command(ShowFilePicker);
DeleteImages = new Command(DeleteFiles);
}
public ImageSource CreatedImage
{
get => image;
set
{
image = value;
OnPropertyChanged();
}
}
public async void ShowFilePicker()
{
FilePickerFileType filePickerFileType = new FilePickerFileType(
new Dictionary<DevicePlatform, IEnumerable<string>> {
{ DevicePlatform.iOS, new [] { "jpeg", "png", "mp3", "mpeg4Movie", "plaintext", "utf8PlainText", "html" } },
{ DevicePlatform.Android, new [] { "image/jpeg", "image/png", "audio/mp3", "audio/mpeg", "video/mp4", "text/*", "text/html" } },
{ DevicePlatform.UWP, new []{ "*.jpg", "*.jpeg", "*.png", "*.mp3", "*.mp4", "*.txt", "*.html" } }
});
PickOptions pickOptions = new PickOptions
{
PickerTitle = "Wählen Sie eine oder mehrere Dateien aus",
FileTypes = filePickerFileType,
};
IEnumerable<FileResult> pickedFiles = await FilePicker.PickMultipleAsync(pickOptions);
List<FileResult> results = pickedFiles.ToList();
if (results != null && results.Count > 0)
{
foreach (FileResult fileResult in results)
{
using (Stream stream = await fileResult.OpenReadAsync())
{
DirectoryInfo directoryInfo = Directory.CreateDirectory(fileFolder);
string directoryPath = directoryInfo.FullName;
string filepath = Path.Combine(directoryPath, fileResult.FileName);
try
{
byte[] bArray = new byte[stream.Length];
using (FileStream fs = new FileStream(filepath, FileMode.OpenOrCreate))
{
stream.Read(bArray, 0, (int)stream.Length);
int length = bArray.Length;
fs.Write(bArray, 0, length);
}
CreatedImage = ImageSource.FromFile(filepath);
}
catch (Exception exc)
{
}
}
}
}
}
public void DeleteFiles()
{
string[] filePaths = Directory.GetFiles(fileFolder);
foreach(string filePath in filePaths)
{
File.Delete(filePath);
}
}
}
}
I already gave my app the access to the filesytem via windows settings, also I gave the Android-part read and write access. I even gave the UWP-part "broadFileAccess" and even that didn't make the cut.
This intersects with another problem, where the UWP part can write files into a folder in "Environment.SpecialFolder.LocalApplicationData", but it isn't allowed to delete the files in this folder.
Does this have something to do with the sandboxes of UWP and Android?
App cannot further process disks / folder in Xamarin
I tested with you code, the problem is the file was using by image control when you delete it, if we disable CreatedImage = ImageSource.FromFile(filepath); this line. it will work as expect.
I need that image control to display the image
We suggest you render image with stream but not create source from file directly.
For example
CreatedImage = ImageSource.FromStream(() => stream);
Related
I'm trying to use the MediaElement to play a video selected from the Gallery. The path to the Video is saved within the app then bound to the MediaElement Source.
<xct:MediaElement Source="{Binding VideoUri, Converter={StaticResource VideoSourceConverter}}"
AutoPlay="False"
ShowsPlaybackControls="True"
Aspect="AspectFit"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand" />
I'm using a converter as described in the documentation:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return null;
if (string.IsNullOrWhiteSpace(value.ToString()))
return null;
if (value.ToString().StartsWith("http"))
return value;
return new Uri($"ms-appdata:///{value}");
}
https://learn.microsoft.com/en-gb/xamarin/community-toolkit/views/mediaelement#play-local-media
But am getting the error: Invalid UriParameter name: Source
The saved path on Android is:
"/storage/emulated/0/Android/data/[app identifier]/files/Movies/temp/[filename].mp4"
Haven't tested in iOS yet.
Any guidance will be much appreciated.
Thanks.
The Converter you used is used for the UWP. UWP could play media files that are located in the app's xxxx folder by prefixing the media file with ms-appdata:///xxxx/.
For mobile devices, when you use the CrossMedia to select the video, you could get the stream directly from the file.
I use a button to do the select operation and then use the INotifyPropertyChanged to update the binding. Here is the code for your reference.
Xaml:
<Button Clicked="Button_Clicked" Text="Select"/>
<xct:MediaElement Source="{Binding VideoUri}"
AutoPlay="False"
ShowsPlaybackControls="True"
Aspect="AspectFit"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand" />
Code:
public partial class Page29 : ContentPage
{
Page29ViewModel viewModel = new Page29ViewModel();
public Page29()
{
InitializeComponent();
this.BindingContext = viewModel;
}
private async void Button_Clicked(object sender, EventArgs e)
{
string path = string.Empty;
MediaFile video = null;
if (CrossMedia.Current.IsPickVideoSupported)
{
video = await CrossMedia.Current.PickVideoAsync();
}
var fileName = "sample";
var newFile = Path.Combine(FileSystem.AppDataDirectory, fileName + ".mp4");
if (!File.Exists(newFile))
{
using (var inputStream = video.GetStream())
{
using (FileStream outputStream = File.Create(newFile))
{
await inputStream.CopyToAsync(outputStream);
}
}
}
viewModel.VideoUri = newFile;
}
}
public class Page29ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _videoUri;
public string VideoUri
{
get { return _videoUri; }
set { _videoUri = value; NotifyPropertyChanged(nameof(VideoUri)); }
}
public Page29ViewModel()
{
}
}
You dont need the converter just return the path to the mediaplayer control .
mediaFile = await this._mediaPicker.PickVideoAsync();
VideoUri = mediaFile.Path;
I using mapsui to show an IGN map on my application and I want to make a screenshot of the map when I click on a specific button.
So I use dependency injection and It works perfectly on UWP.
But with Android, I can't screen the map and I have a white screen.
There is my code for Android :
public Task<byte[]> TakeShot()
{
var _activity = CrossCurrentActivity.Current;
if (_activity == null)
{
throw new Exception("You have to set ScreenshotManager.Activity in your Android project");
}
var view = _activity.Activity.Window.DecorView;
view.DrawingCacheEnabled = true;
Bitmap bitmap = view.GetDrawingCache(true);
view.BuildDrawingCache();
byte[] bitmapData;
using (var stream = new MemoryStream())
{
bitmap.Compress(Bitmap.CompressFormat.Png, 0, stream);
bitmapData = stream.ToArray();
}
return Task.FromResult(bitmapData);
}
Only the map is blank, because I have my button on the image.
Thank's for your help
I used the Mapsui, then I create a screenshot function with dependence service, Here is my GIF when take a screemshot.
Here is my dependence service about achieveing screenshot.
[assembly: Dependency(typeof(ScreenshotService))]
namespace MVVMDataBinding.Droid
{
public class ScreenshotService : IScreenshotService
{
public byte[] Capture()
{
var _activity = CrossCurrentActivity.Current;
var rootView = _activity.Activity.Window.DecorView;
using (var screenshot = Bitmap.CreateBitmap(
rootView.Width,
rootView.Height,
Bitmap.Config.Argb8888))
{
var canvas = new Canvas(screenshot);
rootView.Draw(canvas);
using (var stream = new MemoryStream())
{
screenshot.Compress(Bitmap.CompressFormat.Png, 90, stream);
return stream.ToArray();
}
}
}
}
}
And create an interface IScreenshotService in the xamarin forms.
public interface IScreenshotService
{
byte[] Capture();
}
Use it in the xamarin forms.
private void Button_Clicked(object sender, EventArgs e)
{
var screenshotData = DependencyService.Get<IScreenshotService>().Capture();
myImage.Source=ImageSource.FromStream(() => new
MemoryStream((byte[])screenshotData));
}
forground code.
<ContentPage.Content>
<StackLayout>
<Grid x:Name="ContentGrid" WidthRequest="100" HeightRequest="200" />
<Button Text="Take a screen" Clicked="Button_Clicked"/>
<Image x:Name="myImage" WidthRequest="200" HeightRequest="300"/>
</StackLayout>
</ContentPage.Content>
Here is my demo.
https://github.com/851265601/MapsuiWithScreenshotDemo
<ListView x:Name="PictureListView" HasUnevenRows="True" WidthRequest="320" HeightRequest="400" SeparatorColor="White">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Image Source="{Binding Path}" HeightRequest="80" WidthRequest="80" Aspect="AspectFill" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ObservableCollection<Image> Pictures = new ObservableCollection<Image>(new Image { Source = "file.png" });
PictureListView.ItemSource = Pictures;
private async void PictureListView_ItemTapped(object sender, ItemTappedEventArgs e)
{
var photo = (Image)e.Item;
photo.Source = "file2.png";
}
i am trying to update an image in a listview using xamarin forms....i have the code listed above....but when the click fires, the new image ("file2.png") doesn't get repainted to the screen. Instead, i just an empty space for where the image would have been. How do i change an image out in a listview?
You are using ObservableCollection which will only update the list view when the collection is updated (add, move, remove). In your case, you are only updating the content of the item, so it will not reflect in the screen.
You should implement your code with proper data binding or MVVM. Read more about MVVM HERE.
If you do not want to implement the MVVM, you can use the following tricks.
private async void PictureListView_ItemTapped(object sender, ItemTappedEventArgs e)
{
var photo = (Image)e.Item;
var newImage = new Image { Source = "file2.png" };
var index = Pictures.IndexOf(photo);
Pictures.Remove(photo);
Pictures.Insert(index, newImage);
}
You should implement INotifyPropertyChanged
Here a sample code
using System;
using System.ComponentModel;
using Xamarin.Forms;
namespace XamlSamples
{
class ClockViewModel : INotifyPropertyChanged
{
DateTime dateTime;
public event PropertyChangedEventHandler PropertyChanged;
public ClockViewModel()
{
this.DateTime = DateTime.Now;
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
this.DateTime = DateTime.Now;
return true;
});
}
public DateTime DateTime
{
set
{
if (dateTime != value)
{
dateTime = value;
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs("DateTime"));
}
}
}
get
{
return dateTime;
}
}
}
}
Here some docs
data_bindings_to_mvvm
Im making an app for iOS and android that offers the ability to watch video's. Im currently working on the iOS implementation but each time I dismiss the viewcontroller holding an AVPlayer or MPPlayer the app throws 'System.InvalidOperationException: Sequence contains no matching element'. I have no idea why this happens. Ill share some example code.
Renderer
using AnimeViewer.iOS.CustomRenderers;
using AnimeViewer.Views.Partials;
using AVFoundation;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(VideoPlayer), typeof(VideoPlayerRenderer))]
namespace AnimeViewer.iOS.CustomRenderers
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
public VideoPlayer VideoPlayer { get; set; }
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> e)
{
base.OnElementChanged(e);
if (e.NewElement == null) return;
VideoPlayer = e.NewElement;
SetNativeControl(new UIView {Frame = UIScreen.MainScreen.Bounds});
var player = new AVPlayer(new NSUrl(VideoPlayer.Source));
var playerLayer = new AVPlayerLayer
{
Player = player,
Frame = Control.Frame
};
Control.Layer.AddSublayer(playerLayer);
player.Play();
}
}
}
View
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:partials="clr-namespace:AnimeViewer.Views.Partials;assembly=AnimeViewer"
x:Class="AnimeViewer.Views.VideoPlayerPage">
<partials:VideoPlayer x:Name="Player"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand" />
</ContentPage>
You don't need a custom renderer to call the video player, you can use a DependencyService:
public class VideoPLayer : IVideoPlayer
{
public void Play(string path)
{
var _player = new AVPlayer(NSUrl.FromFilename(path));
var _playerController = new AVPlayerViewController();
_playerController.Player = _player;
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
vc.PresentViewController(_playerController, true, null);
_playerController.View.Frame = vc.View.Frame;
}
}
To learn more about DependencyService you can access the following link: https://developer.xamarin.com/guides/xamarin-forms/dependency-service/
I'm going to use a pre-populated SQLite database in an Android game which I'm creating using Unity .
Since the simple way doesn't work on Android (It works perfect on Windows thou), I've followed this tutorial to use my database in an Android app.
public void OpenDB(string p) //p is the database name
{
// check if file exists in Application.persistentDataPath
string filepath = Application.persistentDataPath + "/" + p;
if(!File.Exists(filepath))
{
// if it doesn't ->
// open StreamingAssets directory and load the db ->
WWW loadDB = new WWW("jar:file://" + Application.dataPath + "!/assets/" + p); // this is the path to your StreamingAssets in android
while(!loadDB.isDone) {} // CAREFUL here, for safety reasons you shouldn't let this while loop unattended, place a timer and error check
// then save to Application.persistentDataPath
File.WriteAllBytes(filepath, loadDB.bytes);
}
//open db connection
connection = "URI=file:" + filepath;
dbcon = new SqliteConnection(connection);
dbcon.Open();
}
When I run this code, I get following error:
SqliteSyntaxException: file is encrypted or is not a database
Here's the full error:
SqliteSyntaxException: file is encrypted or is not a database
Mono.Data.SqliteClient.SqliteCommand.GetNextStatement (IntPtr pzStart,
System.IntPtr& pzTail, System.IntPtr& pStmt)
Mono.Data.SqliteClient.SqliteCommand.ExecuteReader (CommandBehavior
behavior, Boolean want_results, System.Int32& rows_affected)
Mono.Data.SqliteClient.SqliteCommand.ExecuteReader (CommandBehavior
behavior) Mono.Data.SqliteClient.SqliteCommand.ExecuteDbDataReader
(CommandBehavior behavior) System.Data.Common.DbCommand.ExecuteReader
() System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader
() dbAccess.SingleSelectWhere (System.String tableName, System.String
itemToSelect, System.String wCol, System.String wPar, System.String
wValue) (at Assets/dbAccess.cs:152)
I've downloaded the example from that post, and got the same error.
I'm suspicious about this line:
File.WriteAllBytes(filepath, loadDB.bytes);
and my thought is for some reason, it can't write the database data into the file.
Does anyone know how to solve this?
I solved the problem thanks to this blog.
This code works perfect on every platform due to the if statements which will do different behaviors depending on which platform the app is running on.
Here's the DataService.cs which do the important parts (or maybe I better say the whole part)
using SQLite4Unity3d;
using UnityEngine;
#if !UNITY_EDITOR
using System.Collections;
using System.IO;
#endif
using System.Collections.Generic;
public class DataService {
private SQLiteConnection _connection;
public DataService(string DatabaseName){
#if UNITY_EDITOR
var dbPath = string.Format(#"Assets/StreamingAssets/{0}", DatabaseName);
#else
// check if file exists in Application.persistentDataPath
var filepath = string.Format("{0}/{1}", Application.persistentDataPath, DatabaseName);
if (!File.Exists(filepath))
{
Debug.Log("Database not in Persistent path");
// if it doesn't ->
// open StreamingAssets directory and load the db ->
#if UNITY_ANDROID
var loadDb = new WWW("jar:file://" + Application.dataPath + "!/assets/" + DatabaseName); // this is the path to your StreamingAssets in android
while (!loadDb.isDone) { } // CAREFUL here, for safety reasons you shouldn't let this while loop unattended, place a timer and error check
// then save to Application.persistentDataPath
File.WriteAllBytes(filepath, loadDb.bytes);
#elif UNITY_IOS
var loadDb = Application.dataPath + "/Raw/" + DatabaseName; // this is the path to your StreamingAssets in iOS
// then save to Application.persistentDataPath
File.Copy(loadDb, filepath);
#elif UNITY_WP8
var loadDb = Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS
// then save to Application.persistentDataPath
File.Copy(loadDb, filepath);
#elif UNITY_WINRT
var loadDb = Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS
// then save to Application.persistentDataPath
File.Copy(loadDb, filepath);
#else
var loadDb = Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS
// then save to Application.persistentDataPath
File.Copy(loadDb, filepath);
#endif
Debug.Log("Database written");
}
var dbPath = filepath;
#endif
_connection = new SQLiteConnection(dbPath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create);
Debug.Log("Final PATH: " + dbPath);
}
public void CreateDB(){
_connection.DropTable<Person> ();
_connection.CreateTable<Person> ();
_connection.InsertAll (new[]{
new Person{
Id = 1,
Name = "Tom",
Surname = "Perez",
Age = 56
},
new Person{
Id = 2,
Name = "Fred",
Surname = "Arthurson",
Age = 16
},
new Person{
Id = 3,
Name = "John",
Surname = "Doe",
Age = 25
},
new Person{
Id = 4,
Name = "Roberto",
Surname = "Huertas",
Age = 37
}
});
}
public IEnumerable<Person> GetPersons(){
return _connection.Table<Person>();
}
public IEnumerable<Person> GetPersonsNamedRoberto(){
return _connection.Table<Person>().Where(x => x.Name == "Roberto");
}
public Person GetJohnny(){
return _connection.Table<Person>().Where(x => x.Name == "Johnny").FirstOrDefault();
}
public Person CreatePerson(){
var p = new Person{
Name = "Johnny",
Surname = "Mnemonic",
Age = 21
};
_connection.Insert (p);
return p;
}
}
It follows by two other script to create or use the existing database.
ExistingDBScript.cs
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
public class ExistingDBScript : MonoBehaviour {
public Text DebugText;
// Use this for initialization
void Start () {
var ds = new DataService ("existing.db");
//ds.CreateDB ();
var people = ds.GetPersons ();
ToConsole (people);
people = ds.GetPersonsNamedRoberto ();
ToConsole("Searching for Roberto ...");
ToConsole (people);
ds.CreatePerson ();
ToConsole("New person has been created");
var p = ds.GetJohnny ();
ToConsole(p.ToString());
}
private void ToConsole(IEnumerable<Person> people){
foreach (var person in people) {
ToConsole(person.ToString());
}
}
private void ToConsole(string msg){
DebugText.text += System.Environment.NewLine + msg;
Debug.Log (msg);
}
}
CreateDBScript.cs
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
public class CreateDBScript : MonoBehaviour {
public Text DebugText;
// Use this for initialization
void Start () {
StartSync();
}
private void StartSync()
{
var ds = new DataService("tempDatabase.db");
ds.CreateDB();
var people = ds.GetPersons ();
ToConsole (people);
people = ds.GetPersonsNamedRoberto ();
ToConsole("Searching for Roberto ...");
ToConsole (people);
}
private void ToConsole(IEnumerable<Person> people){
foreach (var person in people) {
ToConsole(person.ToString());
}
}
private void ToConsole(string msg){
DebugText.text += System.Environment.NewLine + msg;
Debug.Log (msg);
}
}
And the person script, demonstrates the person table in the database
using SQLite4Unity3d;
public class Person {
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int Age { get; set; }
public override string ToString ()
{
return string.Format ("[Person: Id={0}, Name={1}, Surname={2}, Age={3}]", Id, Name, Surname, Age);
}
}
Also you need to add plugins and Sqlite.cs to your project which you can find in the git repository
It helped me to overcome the issue, Hope it help the others as well.