如何在针对Windows Store应用程序、WP7、WP8和WPF的可移植类库中处理图像?

23

我正在开发一个第一版的 PCL,它可以针对 WSA(Windows 应用商店应用程序)、WPF、WP7 和 WP8 进行定位。我们可以说这是一种类似于名片夹的应用程序,你有联系人,他们有联系方式和图片(虽然它不是这样的,但我不能透露关于应用程序的详细信息,因此我使用了一个非常简单的例子)。以下是我的一些问题:

  1. 我应该把图片放在 PCL 中吗?

如果是的话:

  1. 如何在 WSA 中引用图片以供使用?
  2. 在跨不同项目使用时,如何通过比例限定符等方式进行最佳缩放?

我没有使用数据库,图像也没有从外部服务下载 - 我希望将图像(实际上不多)保存在应用程序或 PCL 中。

编辑: 我只想显示图片。就这样。这是一个静态名片夹,您无法添加新人员。我只想显示 5 个人的图片(在 PCL 中)。如果这是一个 Windows 应用商店应用程序,我应该如何引用这些图片呢?

我有一个绑定,并且 DataContext 设置为 PCL 中的 ViewModel。ViewModel 从模型中聚合要显示的数据。我绑定的属性是 MyImage。忽略其他平台,Uri 会是什么样子?其他一切都正常工作。

我真的只想获得这三个问题的帮助,尽管我非常感激所有人的回答!!!


这只是一个缩短“Windows Store应用程序”这个太长名称的尝试。我已经确保现在在Q中如此说明 :) - Iris Classon
我认为问题可能在于“reference”这个词 - 如果你的意思是Uri?那么可以参考类似于https://dev59.com/NWPVa4cB1Zd3GeqP7aCC的内容。 - Stuart
我的问题仍然是一样的:/,Uri会是什么样子呢?当有人向我解释路径的样子时,我感觉自己很蠢(或者我误解了你的评论吗?) - Iris Classon
你能够在PCL和WinRT应用程序中使其工作吗?我整天尝试了很多版本都没有成功,但也许我错过了什么。我希望有一个可行的Uri示例(一行代码,而不是整个项目)WinRT => PCL中的图像。相对路径在WinRT中不受支持,所以我很好奇还会有什么问题。 - Iris Classon
我现在不确定你在问什么。也许尝试提出一个关于winrt的新问题- 提供在wp7或wpf中工作的XAML和C#代码- 并询问如何将其翻译到winrt。先在rt中使其正常工作,然后再添加PCL。这对我们所有人来说都是新领域,但我们可以一步一步地到达目标。 - Stuart
显示剩余3条评论
7个回答

23

对于许多情况,图像是特定于平台的。它们需要考虑设备本身的大小和DPI,并且需要与应用程序的外观和感觉相匹配。在这些情况下,我会让视图本身决定向用户显示哪些图像,可能基于 ViewModel 提供的某种状态/模式。

但是,在某些情况下,图像需要来自 ViewModel,例如在邮件应用程序中显示的发送者缩略图。在这些情况下,我会让 ViewModel 返回某种平台无关的图像概念(例如 byte []),然后让特定于平台的项目将其转换为其 UI 栈可以理解的内容(在 XAML 中,这将是一个 ImageSource)。

代码大致如下:

可移植项目

using System.IO;
using System.Reflection;

namespace Portable
{
    public class ViewModel
    {
        private byte[] _image = LoadFromResource("Image.png");

        public byte[] Image
        {
            get { return _image; }
        }

        private static byte[] LoadFromResource(string name)
        {
            using (Stream stream = typeof(ViewModel).GetTypeInfo().Assembly.GetManifestResourceStream("Portable." + name))
            {
                MemoryStream buffer = new MemoryStream();
                stream.CopyTo(buffer);

                return buffer.ToArray();
            }
        }
    }
}

提示:根据你的目标平台,你需要删除或添加GetTypeInfo()。

在这里,我们正在从嵌入式资源(属性->生成操作->嵌入式资源)读取,但是你可以想象这可能来自于网络或其他地方。

Windows商店应用程序项目: 在Windows商店应用程序中,您需要一个值转换器将byte[]转换为ImageSource:

using System;
using System.IO;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media.Imaging;

namespace App
{
    public class ByteToImageSourceValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            InMemoryRandomAccessStream s = new InMemoryRandomAccessStream();

            byte[] bytes = (byte[])value;
            Stream stream = s.AsStreamForWrite();
            stream.Write(bytes, 0, bytes.Length);
            stream.Flush();
            stream.Seek(0, SeekOrigin.Begin);


            BitmapImage source = new BitmapImage();
            source.SetSource(s);

            return source;           

        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }
}

在View的代码后台中,设置DataContext:

DataContext = new ViewModel();

然后在视图中绑定到ViewModel.Image属性,并设置转换器:

<Page.Resources>
    <local:ByteToImageSourceValueConverter x:Name="ImageConverter"/>
</Page.Resources>

<Grid >
    <Image HorizontalAlignment="Left" Height="242" Margin="77,10,0,0" VerticalAlignment="Top" Width="278" Source="{Binding Image, Converter={StaticResource ImageConverter}}"/>

</Grid>

1
值得指出的是,对于大多数应用程序,这对性能是一个致命伤,特别是在存在多个图片且图片具有相当大的尺寸时。它也削弱了底层框架将图像缩放到不同设备的能力。我认为更好的解决方案可能是先缓存图像并引用其路径。 - Jerry Nixon

14
图片在需要跨不同像素密度和屏幕尺寸的平台进行缩放时确实存在问题。此外,图像处理库通常存在不可移植性的问题 - 尤其是当它们在每个平台上使用硬件加速时。
假设您的转盘应用程序以某种方式允许用户捕获图像,然后将其上传到某个共享数据库/服务以供以后查看,以下是我处理问题的方法。这不是唯一的解决方案-在此没有“一种正确的方法”!
** 捕获和上传图像 **
  1. 要捕获图片(从文件夹或相机),我必须为每个平台使用本地钩子-因此对于此部分,不要使用PCL
  2. 如果我需要在设备上对图像进行一些处理(例如调整大小或添加缩略图),那么这可能会在每个平台上以不同的方式完成-因此不要使用PCL。
  3. 一旦您有了照片(例如作为在MemoryStream中编码的JPEG),并且想要将其上传到服务器,则我可能会在通用PCL代码中使用异步处理程序HttpWebRequest(这些是旧方法,与async / await无关)
  4. 正在服务器上运行的内容...几乎肯定不是PCL代码-我可能会在那里使用方法将图像调整为设备就绪大小-例如不同大小的缩略图
** 显示图像 **
当我需要从数据库中显示某人的图像时,我可能会让服务器返回可用缩略图URL列表 - 这是服务器代码,因此不属于PCL。
实际上请求联系人列表或联系人详细信息的应用程序代码 - 这可能是PCL代码 - 并且可能再次使用HttpWebRequest。
将联系人显示在屏幕上的视图代码?这可能是XAML - 我只需在每个平台上使用本机Image控件来消耗和呈现适当的缩略图。
** 如果您正在本地存储图像 **
如果您将图像存储在本地而不是使用中央服务器,则可能需要使用非PCL代码。每个平台都有相同基本类型的方法:LoadFile,SaveFile等,并且每个平台都提供通过不同的API访问文件系统(例如WPF中的System.IO,WP7 Silverlight中的IsolatedStorage等)的机制来创建,枚举,读取和写入文件夹和文件。
一旦将文件放入通用结构中,PCL控件代码将能够将每个文件视为一对字符串-它所在的文件夹和文件名...
...在UI层(例如XAML)中,您可能可以直接引用这些图像文件-可能可以使用特定的ValueConverter在每个平台上生成正确的文件名或文件流-例如,在wp7中,您可以使用从Windows Phone 7 Silverlight binding image from the IsolatedStorage转换器。

所以,我的总结是:

  • 我会尽可能使用PCL代码
  • 但实际上,这些PCL代码只会存在于控制和网络部分,而不会存在于图像采集、图像处理或图像渲染部分。

以下是其他可能有帮助的事项:

  • 目前我发现将 .Net4.5 的 async/await 代码与“普通”代码混合在一起相当困难 - 因此即使共享网络代码也可能非常困难 - 如果您为每个功能编写单独的代码,您实际上可以节省时间 :(
  • 为了帮助共享代码,我肯定会尝试使用某种形式的 IoC 或 DI,在需要的地方注入特定平台特定代码的实现(这是我在 MvvmCross 中经常做的事情 - 这就是 @dsplaisted 在http://blogs.msdn.com/b/dsplaisted/archive/2012/08/27/how-to-make-portable-class-libraries-work-for-you.aspx中谈到的服务位置)

我想把这个标记为正确答案,但很难选择,因为这是一个很好的答案。我只想说感谢你的帮助,我可以看出你在回答上花了相当多的时间,从架构角度来看,它提出了一些我应该牢记的事情。我已经点赞了 :) 谢谢! - Iris Classon

2
我倾向于认为图像处理应该是特定于平台的。通常来说,任何与视图相关的内容都不应包含在PCL中。以上答案提供了一些很棒的技术细节,我不再赘述,但是想张贴一个幻灯片链接,讨论使用PCL的原则。希望这能提供关于总体设计策略的一些指导,并加强之前答案中的信息。链接如下:http://prezi.com/ipyzhxvxjvp-/sharing-is-caring-code-reuse-in-xaml-applications/

2
大多数人已经说过,答案也是“不可能”。PCL中的UI元素违背了PCL的哲学,PCL应该可以跨越您正在瞄准的各个平台使用。
以下是MSDN关于PCL的说明:“由于不同设备的用户界面之间的行为差异,可移植类库项目不包含任何UI组件”(来自http://msdn.microsoft.com/en-us/library/gg597391.aspx)。
希望这可以帮助您。

0

你可以将图像作为“object”类型放在视图模型上,并使用一个接口(注入的)来创建位图。

public interface IBitMapCreator
{
    object Create(string path);
}

public class MyViewModel
{
    private bool _triedToSetThumb;
    private readonly IBitMapCreator _bc;

    public MyViewModel(IBitMapCreator bc, string path)
    {
        _bc = bc;
        SetThumb(path);
    }

    public object Thumb { get; private set; }

    private void SetThumb(string path)
    {
        try
        {
            if (!_triedToSetThumb)
            {
                string ext = Path.GetExtension(path).ToUpper();

                if (
                    ext == ".JPG" ||
                    ext == ".JPEG" ||
                    ext == ".PNG" ||
                    ext == ".GIF" ||
                    ext == ".BMP" ||
                    false
                    )
                {
                    Thumb = _bc.Create(path);
                    OnPropertyChanged(new PropertyChangedEventArgs("Thumb"));
                }
            }
        }
        finally
        {
            _triedToSetThumb = true;
        }
    }

}

在 WPF 版本中,你可以这样做。
class BitMapCreator : IBitMapCreator
{
    public object Create(string path)
    {
        return BitmapFrame.Create(new Uri(path));
    }
} 

通过使用该方法,您可以直接将数据绑定到图像上,而无需使用转换器。

0

我的第一反应是,根据你所描述的情况,不应该在你的PCL中使用图像。

但是,如果我要这样做,可以通过不同库中的资源存储图像,并为每个平台存储不同的图像,然后进行环境检查以确定要提取哪个图像。但是,只有在需要扩展时才应尝试此方法。

所谓扩展,是指您正在开源此库,将有成千上万的程序使用它。如果这仅用于您自己内部使用的3或4个应用程序,我会建议您不要费心。只需将图像传递到共享库中或通过配置设置即可完成。

或者干脆不要通过PCL管理图像。因为图像处理在每个平台上都不同,特别是你提到的那些平台。而且你肯定会遇到一些奇怪的错误,导致它在其他平台上的工作方式与其不同。


你知道如何在WinRT应用程序中引用PCL中的图像吗?我只是好奇是否可以这样做。发现很多人都在问这个问题,但是没有关于图像的答案。 - Iris Classon

0

我可能会将实际的图像文件放在每个应用程序中。我认为试图嵌入PCL中不会获得多少价值。

在可移植的ViewModels中,您可以通过Uri引用图像。不幸的是,每个平台上的Uri都需要不同。对于Windows Store应用程序,它需要类似于ms-appx:////Assets/image.png,而对于Windows Phone,我认为它只是/Assets/image.png

因此,您需要一些代码来确定正确的Uri格式。这可以是具有可移植接口的特定于平台的服务,ViewModel将调用该服务。您还可以创建一个绑定转换器,以执行所需的任何转换。

当然还有其他方法可以做到这一点-这只是基于您问题中提供的信息选择的方法。


这就是我目前正在做的事情,通过“适配器”使用抽象化,当设置DataContext时,每个平台都会将其发送到ViewModel中。我猜一个带有静态BaseUri属性的静态类也可以用于这种情况,但是我正在使用适配器来处理文件和存储等内容,所以我可能会保持这种模式以保持一致。 - Iris Classon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接