Xamarin.Forms客戶端第一版

来源:https://www.cnblogs.com/Dotnet9-com/archive/2020/04/03/12624408.html
-Advertisement-
Play Games

Xamarin.Forms客戶端第一版 作為TerminalMACS的一個子進程模塊,目前完成第一版:讀取展示手機基本信息、聯繫人信息、應用程式本地化。 1. 功能簡介 2. 詳細功能說明 3. 關於TerminalMACS 1. 功能簡介 1.1. 讀取手機基本信息 主要使用Xamarin.Ess ...


Xamarin.Forms客戶端第一版

作為TerminalMACS的一個子進程模塊,目前完成第一版:讀取展示手機基本信息、聯繫人信息、應用程式本地化。

  1. 功能簡介
  2. 詳細功能說明
  3. 關於TerminalMACS

1. 功能簡介

1.1. 讀取手機基本信息

主要使用Xamarin.Essentials庫獲取設備基本信息,Xam.Plugin.DeviceInfo插件獲取App Id,其實該插件也能獲取設備基本信息。

1.2. 讀取手機聯繫人信息

Android和iOS工程具體實現聯繫人讀取服務,使用到DependencyService獲取服務功能。

1.3. 應用本地化

使用資源文件實現本地化,目前只做了中、英文。

2. 詳細功能說明

2.1. 讀取手機基本信息

Xamarin.Essentials庫用於獲取手機基本信息,比如手機廠商、型號、名稱、類型、版本等;Xam.Plugin.DeviceInfo插件獲取App Id,用於唯一標識不同手機,獲取信息見下圖:

代碼結構如下圖:

ClientInfoViewModel.cs

using Plugin.DeviceInfo;
using System;
using Xamarin.Essentials;

namespace TerminalMACS.Clients.App.ViewModels
{
    /// <summary>
    /// Client base information page ViewModel
    /// </summary>
    public class ClientInfoViewModel : BaseViewModel
    {
        /// <summary>
        /// Gets or sets the id of the application.
        /// </summary>
        public string AppId { get; set; } = CrossDeviceInfo.Current.GenerateAppId();
        /// <summary>
        /// Gets or sets the model of the device.
        /// </summary>
        public string Model { get; private set; } = DeviceInfo.Model;
        /// <summary>
        /// Gets or sets the manufacturer of the device.
        /// </summary>
        public string Manufacturer { get; private set; } = DeviceInfo.Manufacturer;
        /// <summary>
        /// Gets or sets the name of the device.
        /// </summary>
        public string Name { get; private set; } = DeviceInfo.Name;
        /// <summary>
        /// Gets or sets the version of the operating system.
        /// </summary>
        public string VersionString { get; private set; } = DeviceInfo.VersionString;
        /// <summary>
        /// Gets or sets the version of the operating system.
        /// </summary>
        public Version Version { get; private set; } = DeviceInfo.Version;
        /// <summary>
        /// Gets or sets the platform or operating system of the device.
        /// </summary>
        public DevicePlatform Platform { get; private set; } = DeviceInfo.Platform;
        /// <summary>
        /// Gets or sets the idiom of the device.
        /// </summary>
        public DeviceIdiom Idiom { get; private set; } = DeviceInfo.Idiom;
        /// <summary>
        /// Gets or sets the type of device the application is running on.
        /// </summary>
        public DeviceType DeviceType { get; private set; } = DeviceInfo.DeviceType;
    }
}

ClientInfoPage.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"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:resources="clr-namespace:TerminalMACS.Clients.App.Resx"
             xmlns:vm="clr-namespace:TerminalMACS.Clients.App.ViewModels"
             mc:Ignorable="d"
             x:Class="TerminalMACS.Clients.App.Views.ClientInfoPage"
             Title="{x:Static resources:AppResource.Title_ClientInfoPage}">
    <ContentPage.BindingContext>
        <vm:ClientInfoViewModel/>
    </ContentPage.BindingContext>
    <ContentPage.Content>
        <StackLayout>
            <Label Text="{x:Static resources:AppResource.AppId}"/>
            <Label Text="{Binding AppId}" FontAttributes="Bold" Margin="10,0,0,10"/>
            
            <Label Text="{x:Static resources:AppResource.DeviceModel}"/>
            <Label Text="{Binding Model}" FontAttributes="Bold" Margin="10,0,0,10"/>

            <Label Text="{x:Static resources:AppResource.DeviceManufacturer}"/>
            <Label Text="{Binding Manufacturer}" FontAttributes="Bold" Margin="10,0,0,10"/>
            
            <Label Text="{x:Static resources:AppResource.DeviceName}"/>
            <Label Text="{Binding Name}" FontAttributes="Bold" Margin="10,0,0,10"/>
            
            <Label Text="{x:Static resources:AppResource.DeviceVersionString}"/>
            <Label Text="{Binding VersionString}" FontAttributes="Bold" Margin="10,0,0,10"/>

            <Label Text="{x:Static resources:AppResource.DevicePlatform}"/>
            <Label Text="{Binding Platform}" FontAttributes="Bold" Margin="10,0,0,10"/>
            
            <Label Text="{x:Static resources:AppResource.DeviceIdiom}"/>
            <Label Text="{Binding Idiom}" FontAttributes="Bold" Margin="10,0,0,10"/>
            
            <Label Text="{x:Static resources:AppResource.DeviceType}"/>
            <Label Text="{Binding DeviceType}" FontAttributes="Bold" Margin="10,0,0,10"/>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

2.2. 讀取手機聯繫人信息

Android和iOS工程具體實現聯繫人讀取服務,使用到DependencyService獲取服務功能,功能截圖如下:

2.2.1. TerminalMACS.Clients.App

代碼結構如下圖:

2.2.1.1. 聯繫人實體類:Contacts.cs

目前只獲取聯繫人名稱、圖片、電子郵件(可能多個)、電話號碼(可能多個),更多可以擴展。

namespace TerminalMACS.Clients.App.Models
{
    /// <summary>
    /// Contact information entity.
    /// </summary>
    public class Contact
    {
        /// <summary>
        /// Gets or sets the name
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// Gets or sets the image
        /// </summary>
        public string Image { get; set; }
        /// <summary>
        /// Gets or sets the emails
        /// </summary>
        public string[] Emails { get; set; }
        /// <summary>
        /// Gets or sets the phone numbers
        /// </summary>
        public string[] PhoneNumbers { get; set; }
    }
}
2.2.1.2. 聯繫人服務介面:IContactsService.cs

包括:

  • 一個聯繫人獲取請求介面:RetrieveContactsAsync
  • 一個讀取一條聯繫人結果通知事件:OnContactLoaded

該介面由具體平臺(Android和iOS)實現。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using TerminalMACS.Clients.App.Models;

namespace TerminalMACS.Clients.App.Services
{
    /// <summary>
    /// Read a contact record notification event parameter.
    /// </summary>
    public class ContactEventArgs:EventArgs
    {
        public Contact Contact { get; }
        public ContactEventArgs(Contact contact)
        {
            Contact = contact;
        }
    }

    /// <summary>
    /// Contact service interface, which is required for Android and iOS terminal specific 
    ///  contact acquisition service needs to implement this interface.
    /// </summary>
    public interface IContactsService
    {
        /// <summary>
        /// Read a contact record and notify the shared library through this event.
        /// </summary>
        event EventHandler<ContactEventArgs> OnContactLoaded;
        /// <summary>
        /// Loading or not
        /// </summary>
        bool IsLoading { get; }
        /// <summary>
        /// Try to get all contact information
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? token = null);
    }
}
2.2.1.3. 聯繫人VM:ContactViewModel.cs

VM提供下麵兩個功能:

  1. 全部聯繫人載入。
  2. 聯繫人關鍵字查詢。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using TerminalMACS.Clients.App.Models;
using TerminalMACS.Clients.App.Resx;
using TerminalMACS.Clients.App.Services;
using Xamarin.Forms;

namespace TerminalMACS.Clients.App.ViewModels
{
    /// <summary>
    /// Contact page ViewModel
    /// </summary>
    public class ContactViewModel : BaseViewModel
    {
        /// <summary>
        /// Contact service interface
        /// </summary>
        IContactsService _contactService;
        private string _SearchText;
        /// <summary>
        /// Gets or sets the search text of the contact list.
        /// </summary>
        public string SearchText
        {
            get { return _SearchText; }
            set
            {
                SetProperty(ref _SearchText, value);
            }
        }
        /// <summary>
        /// The search contact command.
        /// </summary>
        public ICommand RaiseSearchCommand { get; }
        /// <summary>
        /// The contact list.
        /// </summary>
        public ObservableCollection<Contact> Contacts { get; set; }
        private List<Contact> _FilteredContacts;
        /// <summary>
        /// Contact filter list.
        /// </summary>
        public List<Contact> FilteredContacts

        {
            get { return _FilteredContacts; }
            set
            {
                SetProperty(ref _FilteredContacts, value);
            }
        }
        public ContactViewModel()
        {
            _contactService = DependencyService.Get<IContactsService>();
            Contacts = new ObservableCollection<Contact>();
            Xamarin.Forms.BindingBase.EnableCollectionSynchronization(Contacts, null, ObservableCollectionCallback);
            _contactService.OnContactLoaded += OnContactLoaded;
            LoadContacts();
            RaiseSearchCommand = new Command(RaiseSearchHandle);
        }

        /// <summary>
        /// Filter contact list
        /// </summary>
        void RaiseSearchHandle()
        {
            if (string.IsNullOrEmpty(SearchText))
            {
                FilteredContacts = Contacts.ToList();
                return;
            }

            Func<Contact, bool> checkContact = (s) =>
            {
                if (!string.IsNullOrWhiteSpace(s.Name) && s.Name.ToLower().Contains(SearchText.ToLower()))
                {
                    return true;
                }
                else if (s.PhoneNumbers.Length > 0 && s.PhoneNumbers.ToList().Exists(cu => cu.ToString().Contains(SearchText)))
                {
                    return true;
                }
                return false;
            };
            FilteredContacts = Contacts.ToList().Where(checkContact).ToList();
        }

        /// <summary>
        /// BindingBase.EnableCollectionSynchronization
        ///     Enable cross thread updates for collections
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="context"></param>
        /// <param name="accessMethod"></param>
        /// <param name="writeAccess"></param>
        void ObservableCollectionCallback(IEnumerable collection, object context, Action accessMethod, bool writeAccess)
        {
            // `lock` ensures that only one thread access the collection at a time
            lock (collection)
            {
                accessMethod?.Invoke();
            }
        }

        /// <summary>
        /// Received a event notification that a contact information was successfully read.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnContactLoaded(object sender, ContactEventArgs e)
        {
            Contacts.Add(e.Contact);
            RaiseSearchHandle();
        }

        /// <summary>
        /// Read contact information asynchronously
        /// </summary>
        /// <returns></returns>
        async Task LoadContacts()
        {
            try
            {
                await _contactService.RetrieveContactsAsync();
            }
            catch (TaskCanceledException)
            {
                Console.WriteLine(AppResource.TaskCancelled);
            }
        }
    }
}
2.2.1.4. 聯繫人展示頁面:ContactPage.xaml

簡單的佈局,一個StackLayout佈局容器豎直排列,一個SearchBar提供關鍵字搜索功能。

<?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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:resources="clr-namespace:TerminalMACS.Clients.App.Resx"
             xmlns:vm="clr-namespace:TerminalMACS.Clients.App.ViewModels"
             xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
             mc:Ignorable="d"
             Title="{x:Static resources:AppResource.Title_ContactPage}"
             x:Class="TerminalMACS.Clients.App.Views.ContactPage"
             ios:Page.UseSafeArea="true">
    <ContentPage.BindingContext>
        <vm:ContactViewModel/>
    </ContentPage.BindingContext>
    <ContentPage.Content>
        <StackLayout>
            <SearchBar x:Name="filterText"
                        HeightRequest="40"
                        Text="{Binding SearchText}"
                       SearchCommand="{Binding RaiseSearchCommand}"/>
            <ListView   ItemsSource="{Binding FilteredContacts}"
                        HasUnevenRows="True">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <StackLayout Padding="10"
                                         Orientation="Horizontal">
                                <Image  Source="{Binding Image}"
                                        VerticalOptions="Center"
                                        x:Name="image"
                                        Aspect="AspectFit"
                                        HeightRequest="60"/>
                                <StackLayout VerticalOptions="Center">
                                    <Label Text="{Binding Name}"
                                       FontAttributes="Bold"/>
                                    <Label Text="{Binding PhoneNumbers[0]}"/>
                                    <Label Text="{Binding Emails[0]}"/>
                                </StackLayout>
                            </StackLayout>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

2.2.2. Android

代碼結構如下圖:

  • AndroidManifest.xml:寫入讀、寫聯繫人許可權請求。
  • ContactsService.cs:具體的聯繫人許可權請求、數據讀取操作。
  • MainActivity.cs:接收許可權請求結果
  • MainApplicaion.cs:此類未添加任務關鍵代碼,但必不可少,否則無法正確彈出許可權請求視窗。
  • PermissionUtil.cs:許可權請求結果判斷

2.2.2.1. AndroidManifest.xml添加許可權

只添加下麵這一行即可:

<uses-permission android:name="android.permission.READ_CONTACTS" />

2.2.2.2. ContactsService.cs

Android聯繫人獲取實現服務,實現IContactsService。註意命名空間上的特性代碼,必須添加上這個特性後,在前面的聯繫人VM中才能使用DependencyService.Get()獲取此服務實例,預設服務是單例的:

[assembly: Xamarin.Forms.Dependency(typeof(TerminalMACS.Clients.App.iOS.Services.ContactsService))]
using Contacts;
using Foundation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TerminalMACS.Clients.App.Models;
using TerminalMACS.Clients.App.Services;

[assembly: Xamarin.Forms.Dependency(typeof(TerminalMACS.Clients.App.iOS.Services.ContactsService))]
namespace TerminalMACS.Clients.App.iOS.Services
{
    /// <summary>
    /// Contact service.
    /// </summary>
    public class ContactsService : NSObject, IContactsService
    {
        const string ThumbnailPrefix = "thumb";

        bool requestStop = false;

        public event EventHandler<ContactEventArgs> OnContactLoaded;

        bool _isLoading = false;
        public bool IsLoading => _isLoading;

        /// <summary>
        /// Asynchronous request permission
        /// </summary>
        /// <returns></returns>
        public async Task<bool> RequestPermissionAsync()
        {
            var status = CNContactStore.GetAuthorizationStatus(CNEntityType.Contacts);

            Tuple<bool, NSError> authotization = new Tuple<bool, NSError>(status == CNAuthorizationStatus.Authorized, null);

            if (status == CNAuthorizationStatus.NotDetermined)
            {
                using (var store = new CNContactStore())
                {
                    authotization = await store.RequestAccessAsync(CNEntityType.Contacts);
                }
            }
            return authotization.Item1;

        }

        /// <summary>
        /// Request contact asynchronously. This method is called by the interface.
        /// </summary>
        /// <param name="cancelToken"></param>
        /// <returns></returns>
        public async Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? cancelToken = null)
        {
            requestStop = false;

            if (!cancelToken.HasValue)
                cancelToken = CancellationToken.None;

            // We create a TaskCompletionSource of decimal
            var taskCompletionSource = new TaskCompletionSource<IList<Contact>>();

            // Registering a lambda into the cancellationToken
            cancelToken.Value.Register(() =>
            {
                // We received a cancellation message, cancel the TaskCompletionSource.Task
                requestStop = true;
                taskCompletionSource.TrySetCanceled();
            });

            _isLoading = true;

            var task = LoadContactsAsync();

            // Wait for the first task to finish among the two
            var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
            _isLoading = false;

            return await completedTask;

        }

        /// <summary>
        /// Load contacts asynchronously, fact reading method of address book.
        /// </summary>
        /// <returns></returns>
        async Task<IList<Contact>> LoadContactsAsync()
        {
            IList<Contact> contacts = new List<Contact>();
            var hasPermission = await RequestPermissionAsync();
            if (hasPermission)
            {

                NSError error = null;
                var keysToFetch = new[] { CNContactKey.PhoneNumbers, CNContactKey.GivenName, CNContactKey.FamilyName, CNContactKey.EmailAddresses, CNContactKey.ImageDataAvailable, CNContactKey.ThumbnailImageData };

                var request = new CNContactFetchRequest(keysToFetch: keysToFetch);
                request.SortOrder = CNContactSortOrder.GivenName;

                using (var store = new CNContactStore())
                {
                    var result = store.EnumerateContacts(request, out error, new CNContactStoreListContactsHandler((CNContact c, ref bool stop) =>
                    {

                        string path = null;
                        if (c.ImageDataAvailable)
                        {
                            path = path = Path.Combine(Path.GetTempPath(), $"{ThumbnailPrefix}-{Guid.NewGuid()}");

                            if (!File.Exists(path))
                            {
                                var imageData = c.ThumbnailImageData;
                                imageData?.Save(path, true);


                            }
                        }

                        var contact = new Contact()
                        {
                            Name = string.IsNullOrEmpty(c.FamilyName) ? c.GivenName : $"{c.GivenName} {c.FamilyName}",
                            Image = path,
                            PhoneNumbers = c.PhoneNumbers?.Select(p => p?.Value?.StringValue).ToArray(),
                            Emails = c.EmailAddresses?.Select(p => p?.Value?.ToString()).ToArray(),

                        };

                        if (!string.IsNullOrWhiteSpace(contact.Name))
                        {
                            OnContactLoaded?.Invoke(this, new ContactEventArgs(contact));

                            contacts.Add(contact);
                        }

                        stop = requestStop;

                    }));
                }
            }

            return contacts;
        }


    }
}

2.2.2.3. MainActivity.cs

代碼簡單,只在OnRequestPermissionsResult方法中接收許可權請求結果:

// The contact service processes the result of the permission request.
ContactsService.OnRequestPermissionsResult(requestCode, permissions, grantResults);

2.2.3. iOS

代碼結構如下圖:

  • ContactsService.cs:具體的聯繫人許可權請求、數據讀取操作。
  • Info.plist:許可權請求時描述文件

2.2.3.1. ContactsService.cs

iOS具體的聯繫人讀取服務,實現IContactsService介面,原理同Android聯繫人服務類似,本人無調試環境,iOS此功能未測試。

using Contacts;
using Foundation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TerminalMACS.Clients.App.Models;
using TerminalMACS.Clients.App.Services;

[assembly: Xamarin.Forms.Dependency(typeof(TerminalMACS.Clients.App.iOS.Services.ContactsService))]
namespace TerminalMACS.Clients.App.iOS.Services
{
    /// <summary>
    /// Contact service.
    /// </summary>
    public class ContactsService : NSObject, IContactsService
    {
        const string ThumbnailPrefix = "thumb";

        bool requestStop = false;

        public event EventHandler<ContactEventArgs> OnContactLoaded;

        bool _isLoading = false;
        public bool IsLoading => _isLoading;

        /// <summary>
        /// Asynchronous request permission
        /// </summary>
        /// <returns></returns>
        public async Task<bool> RequestPermissionAsync()
        {
            var status = CNContactStore.GetAuthorizationStatus(CNEntityType.Contacts);

            Tuple<bool, NSError> authotization = new Tuple<bool, NSError>(status == CNAuthorizationStatus.Authorized, null);

            if (status == CNAuthorizationStatus.NotDetermined)
            {
                using (var store = new CNContactStore())
                {
                    authotization = await store.RequestAccessAsync(CNEntityType.Contacts);
                }
            }
            return authotization.Item1;

        }

        /// <summary>
        /// Request contact asynchronously. This method is called by the interface.
        /// </summary>
        /// <param name="cancelToken"></param>
        /// <returns></returns>
        public async Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? cancelToken = null)
        {
            requestStop = false;

            if (!cancelToken.HasValue)
                cancelToken = CancellationToken.None;

            // We create a TaskCompletionSource of decimal
            var taskCompletionSource = new TaskCompletionSource<IList<Contact>>();

            // Registering a lambda into the cancellationToken
            cancelToken.Value.Register(() =>
            {
                // We received a cancellation message, cancel the TaskCompletionSource.Task
                requestStop = true;
                taskCompletionSource.TrySetCanceled();
            });

            _isLoading = true;

            var task = LoadContactsAsync();

            // Wait for the first task to finish among the two
            var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
            _isLoading = false;

            return await completedTask;

        }

        /// <summary>
        /// Load contacts asynchronously, fact reading method of address book.
        /// </summary>
        /// <returns></returns>
        async Task<IList<Contact>> LoadContactsAsync()
        {
            IList<Contact> contacts = new List<Contact>();
            var hasPermission = await RequestPermissionAsync();
            if (hasPermission)
            {

                NSError error = null;
                var keysToFetch = new[] { CNContactKey.PhoneNumbers, CNContactKey.GivenName, CNContactKey.FamilyName, CNContactKey.EmailAddresses, CNContactKey.ImageDataAvailable, CNContactKey.ThumbnailImageData };

                var request = new CNContactFetchRequest(keysToFetch: keysToFetch);
                request.SortOrder = CNContactSortOrder.GivenName;

                using (var store = new CNContactStore())
                {
                    var result = store.EnumerateContacts(request, out error, new CNContactStoreListContactsHandler((CNContact c, ref bool stop) =>
                    {

                        string path = null;
                        if (c.ImageDataAvailable)
                        {
                            path = path = Path.Combine(Path.GetTempPath(), $"{ThumbnailPrefix}-{Guid.NewGuid()}");

                            if (!File.Exists(path))
                            {
                                var imageData = c.ThumbnailImageData;
                                imageData?.Save(path, true);


                            }
                        }

                        var contact = new Contact()
                        {
                            Name = string.IsNullOrEmpty(c.FamilyName) ? c.GivenName : $"{c.GivenName} {c.FamilyName}",
                            Image = path,
                            PhoneNumbers = c.PhoneNumbers?.Select(p => p?.Value?.StringValue).ToArray(),
                            Emails = c.EmailAddresses?.Select(p => p?.Value?.ToString()).ToArray(),

                        };

                        if (!string.IsNullOrWhiteSpace(contact.Name))
                        {
                            OnContactLoaded?.Invoke(this, new ContactEventArgs(contact));

                            contacts.Add(contact);
                        }

                        stop = requestStop;

                    }));
                }
            }

            return contacts;
        }
    }
}

2.2.3.2. Info.plist

聯繫人許可權請求說明
Info.plist

2.3. 應用本地化

使用資源文件實現本地化,目前只做了中、英文。

資源文件如下:

指定預設區域性

要使資源文件可正常使用,應用程式必須指定 NeutralResourcesLanguage。 在共用項目中,應自定義 AssemblyInfo.cs 文件以指定預設區域性 。 以下代碼演示如何在 AssemblyInfo.cs 文件中將 NeutralResourcesLanguage 設置為 zh-CN (摘自官方文檔:https://docs.microsoft.com/zh-cn/samples/xamarin/xamarin-forms-samples/usingresxlocalization/,後經測試,註釋下麵這段代碼也能正常本地化):

[assembly: NeutralResourcesLanguage("zh-CN")]

XAML中使用

引入資源文件命名空間

xmlns:resources="clr-namespace:TerminalMACS.Clients.App.Resx"

具體使用如

<Label Text="{x:Static resources:AppResource.ClientName_AboutPage}" FontAttributes="Bold"/>

3. 關於TerminalMACS及本客戶端

3.1. TermainMACS

多終端資源管理與檢測系統,包含多個子進程模塊,目前只開發了Xamarin.Forms客戶端,下一步開發服務端,使用 .NET 5 Web API開發,基於Abp vNext搭建。

3.2. Xamarin.Forms客戶端

作為TerminalMACS系統的一個子進程模塊,目前只開發了手機基本信息獲取、聯繫人信息獲取、本地化功能,後續開發服務端時,會配合添加通信功能,比如連接服務端驗證、主動推送已獲取資源等。

3.3. 關於項目開源


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、環境搭建 1.1、由於RabbitMQ是使用Erlang語言開發的,因此要安裝Erlang運行時環境,下載地址:Erlang官網下載 CSDN分享下載 1.2、去RabbitMQ官網下載RabbitMQ Server服務端程式,選擇合適的平臺版本下載並安裝。 RabbitMQ安裝時,會自動在Wi ...
  • 我在面試別人的時候,經常會問對方,如何設計一個秒殺系統?回答的好的同學並不多,這裡我簡要說一下考察這個問題的目的.秒殺系統,那麼顧名思義就是搶購,庫存有限情況下的競爭問題,其實就是一個高併發的處理. 首先我們模擬不做併發處理的情況: 比如我們用戶一個庫存表 stock,庫存數量5 我們對外提供了一個 ...
  • 本文介紹通過C# 編程如何在PPT幻燈片中添加超鏈接的方法,添加鏈接時,可給文本或者圖片添加超鏈接,鏈接對象可指向網頁地址、郵件地址、指定幻燈片等,此外,也可以參考文中編輯、刪除幻燈片中已有超鏈接的方法。 程式使用類庫:Free Spire.Presentation for .NET (免費版) d ...
  • 如何處理幾十萬條併發數據? 答:用存儲過程或事務。取得最大標識的時候同時更新..註意主鍵不是自增量方式這種方法併發的時候是不會有重覆主鍵的..取得最大標識要有一個存儲過程來獲取. 2.寫出一條Sql語句,取出表A中第31到第40記錄(SQLServer,以自動增長的ID作為主鍵,註意:數據不是連續的 ...
  • 有時我們臨時需要一個 JSON 字元串,直接拼接肯定不是好方法,但又懶得去定義一個類,這是用 就會非常的方便。 但是在 中添加數組卻經常被坑。 輸出結果: 非常正確,但如果把 換成 就不對了。 這麼寫會報: Could not determine JSON object type for type ...
  • 0、概述 先瞭解下https是個啥: https://www.bilibili.com/video/BV1j7411H7vV so!只要給我們的web伺服器配置一個證書就行了,證書可以買,也可以用免費的Let's Encrypt,此證書提供商是多個牛X大公司為了推進全球https化搞出來的,所以不用 ...
  • HTTP Method 較為簡單,我們常用的習慣如下: 一般查詢我們都會使用 GET 方法, 創建新的記錄使用 POST 方法 更新已有數據使用 PUT 方法 更新已有數據部分屬性使用 PATCH 方法 刪除已有數據使用 DELETE 方法 下麵來詳細介紹一下常用的 HTTP 狀態碼 1xx 1xx ...
  • 最近是真的比較閑,花了點時間算是把我自己的微博庫的 nuget 包的坑填上了(https://github.com/h82258652/HN.Social.Weibo 歡迎大佬來 Star)。dino 大佬也一直忽悠我弄動畫,可惜我沒啥藝術細胞而且 Composition API 也不太熟悉,就只能 ...
一周排行
    -Advertisement-
    Play Games
  • 1. 說明 /* Performs operations on System.String instances that contain file or directory path information. These operations are performed in a cross-pla ...
  • 視頻地址:【WebApi+Vue3從0到1搭建《許可權管理系統》系列視頻:搭建JWT系統鑒權-嗶哩嗶哩】 https://b23.tv/R6cOcDO qq群:801913255 一、在appsettings.json中設置鑒權屬性 /*jwt鑒權*/ "JwtSetting": { "Issuer" ...
  • 引言 集成測試可在包含應用支持基礎結構(如資料庫、文件系統和網路)的級別上確保應用組件功能正常。 ASP.NET Core 通過將單元測試框架與測試 Web 主機和記憶體中測試伺服器結合使用來支持集成測試。 簡介 集成測試與單元測試相比,能夠在更廣泛的級別上評估應用的組件,確認多個組件一起工作以生成預 ...
  • 在.NET Emit編程中,我們探討了運算操作指令的重要性和應用。這些指令包括各種數學運算、位操作和比較操作,能夠在動態生成的代碼中實現對數據的處理和操作。通過這些指令,開發人員可以靈活地進行算術運算、邏輯運算和比較操作,從而實現各種複雜的演算法和邏輯......本篇之後,將進入第七部分:實戰項目 ...
  • 前言 多表頭表格是一個常見的業務需求,然而WPF中卻沒有預設實現這個功能,得益於WPF強大的控制項模板設計,我們可以通過修改控制項模板的方式自己實現它。 一、需求分析 下圖為一個典型的統計表格,統計1-12月的數據。 此時我們有一個需求,需要將月份按季度劃分,以便能夠直觀地看到季度統計數據,以下為該需求 ...
  • 如何將 ASP.NET Core MVC 項目的視圖分離到另一個項目 在當下這個年代 SPA 已是主流,人們早已忘記了 MVC 以及 Razor 的故事。但是在某些場景下 SSR 還是有意想不到效果。比如某些靜態頁面,比如追求首屏載入速度的時候。最近在項目中回歸傳統效果還是不錯。 有的時候我們希望將 ...
  • System.AggregateException: 發生一個或多個錯誤。 > Microsoft.WebTools.Shared.Exceptions.WebToolsException: 生成失敗。檢查輸出視窗瞭解更多詳細信息。 內部異常堆棧跟蹤的結尾 > (內部異常 #0) Microsoft ...
  • 引言 在上一章節我們實戰了在Asp.Net Core中的項目實戰,這一章節講解一下如何測試Asp.Net Core的中間件。 TestServer 還記得我們在集成測試中提供的TestServer嗎? TestServer 是由 Microsoft.AspNetCore.TestHost 包提供的。 ...
  • 在發現結果為真的WHEN子句時,CASE表達式的真假值判斷會終止,剩餘的WHEN子句會被忽略: CASE WHEN col_1 IN ('a', 'b') THEN '第一' WHEN col_1 IN ('a') THEN '第二' ELSE '其他' END 註意: 統一各分支返回的數據類型. ...
  • 在C#編程世界中,語法的精妙之處往往體現在那些看似微小卻極具影響力的符號與結構之中。其中,“_ =” 這一組合突然出現還真不知道什麼意思。本文將深入剖析“_ =” 的含義、工作原理及其在實際編程中的廣泛應用,揭示其作為C#語法奇兵的重要角色。 一、下劃線 _:神秘的棄元符號 下劃線 _ 在C#中並非 ...