Статьи

Создание Wallview для изображений с помощью Silverlight: код

Добро пожаловать во вторую часть создания Wallview в стиле iTunes. В этой части мы собираемся написать весь код на C # для двух пользовательских элементов управления, которые мы создали с помощью Expression Blend в предыдущем уроке .


Взгляните еще раз на видео-демонстрацию конечного результата, к которому мы будем стремиться (или просто посмотрите демонстрацию на веб-странице выше):


Откройте Visual Studio и нажмите «Файл»> «Открыть»> «Проект / Решение» в строке меню.

Затем перейдите к проекту, который мы создали в первой части этого урока . Мы назвали это «WallviewApp».


В правой части Visual Studio по умолчанию находится обозреватель решений. Здесь вы можете увидеть каждый файл из проекта. Откройте два * .cs-файла «image.xaml.cs» и «wallview-img.xaml.cs» из двух пользовательских элементов управления, которые мы создали в предыдущем руководстве по Blend, а также из «MainPage.xaml.cs». который был предоставлен автоматически.

Открыв эти три файла, вы увидите, что все три класса довольно пусты.


Давайте начнем с кодирования класса изображения. Убедитесь, что вы в настоящее время редактируете файл image.xaml.cs и добавьте следующие объявления прямо над конструктором public image ():

1
2
3
4
public bool selected;
private double defaultSize;
private wallview_img wallview;
private SolidColorBrush colorDefault, colorSelected;

Теперь мы собираемся запрограммировать конструктор, который имеет два параметра: BitmapImage и String. Кроме того, ранее объявленные переменные инициализируются, параметры из конструктора присваиваются источнику «img» и тексту текстового блока «imgName». Мы также регистрируем обработчик события MouseLeftButtonDown для «img»:

1
public image(BitmapImage src, String name) { // Required to initialize variables InitializeComponent();

Параметр BitmapImage подчеркнут красным цветом, поскольку Visual Studio не может найти пространство имен. Чтобы это исправить, просто нажмите слово BitmapImage и маленький синий прямоугольник должен появиться под буквой B:

Когда вы наводите курсор мыши на маленький прямоугольник, появится раскрывающееся меню. Нажмите на запись «Использование System.Windows.Media.Imaging;»:

Теперь Visual Studio знает это пространство имен, и красное подчеркивание исчезнет.


Сразу после конструктора мы напишем пару методов, которые нам понадобятся позже. Методы довольно информативны по названиям методов. image_MouseLeftButtonDown является обработчиком события MouseLeftButtonDown события «img», которое мы зарегистрировали в конструкторе. Он в основном контролирует, что происходит при нажатии на изображение, в зависимости от его состояния (независимо от того, выбрано оно уже или нет):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public void setWallview(wallview_img wv)
{
    wallview = wv;
}
 
public void changeSize(double newsize)
{
    this.imgName.Width = defaultSize * newsize;
    this.imgSize.Width = defaultSize * newsize;
    this.imgSize.Height = defaultSize * newsize;
}
 
public void unselectImage()
{
    selected = false;
    this.imgBorder.BorderBrush = colorDefault;
}
 
public void selectImage()
{
    selected = true;
    this.imgBorder.BorderBrush = colorSelected;
}
 
private void image_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    wallview.imageSelected = true;
    if (wallview.ctrlPressed == true)
    {
        if (selected == true)
            unselectImage();
 
        else
            selectImage();
    }
    else
    {
        wallview.unselectAllImages();
        selectImage();
    }
    e.Handled = true;
}

Теперь мы закончили с классом изображения.

Не беспокойтесь о красных подчеркнутых частях в вашем коде. Эти переменные и методы еще не существуют, и поэтому Visual Studio их не знает, но мы собираемся изменить это в ближайшее время.


Поскольку мы закончили с классом изображения, откройте документ «wallview-img.xaml.cs». Сначала напишите объявления переменных прямо над конструктором «public wallview_img ()»:

1
2
3
4
public List<image> imageList;
private List<StackPanel> panelList;
public bool ctrlPressed, imageSelected;
private double defaultSize, changeSize;

Как и раньше, с BitmapImage Visual Studio не знает пространства имен List . Чтобы исправить это, как и прежде, щелкните один из списков, затем синий прямоугольник и выберите «using System.Collections.Generic;» из меню:


Добавьте следующий код, который инициализирует ранее объявленные переменные, регистрирует некоторые события и отключает кнопки на панели навигации:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public wallview_img()
{
    // Required to initialize variables
    InitializeComponent();
 
    ctrlPressed = false;
    imageSelected = false;
 
    imageList = new List<image>();
    panelList = new List<StackPanel>();
 
    defaultSize = 200;
    changeSize = 1;
 
    //registering all the events
    MouseLeftButtonUp += new MouseButtonEventHandler(wallviewMouseLeftButtonUp);
    SizeChanged += new SizeChangedEventHandler(resizeScrollViewer);
    KeyDown += new KeyEventHandler(keyDownEvent);
    KeyUp += new KeyEventHandler(keyUpEvent);
    this.slider.ValueChanged += new RoutedPropertyChangedEventHandler<double>(sliderValueChanged);
 
    //buttons we dont need yet
    this.btnAllAlbums.IsEnabled = false;
    this.btnCurrentAlbum.IsEnabled = false;
    this.btnNext.IsEnabled = false;
    this.btnPrev.IsEnabled = false;
}

Добавьте следующие методы ниже конструктора. Комментарий перед каждым методом объясняет, что делает метод:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//adds an image to the imagelist and calls resizeimages() which basically adds it to the last stackpanel
public void addImage(image img)
{
    img.imgName.Width = 200;
    img.setWallview(this);
    imageList.Add(img);
 
    resizeImages();
}
 
//clears the whole drawn content, every panel, albumlist and panellist
public void clearLists()
{
    imageList.Clear();
    foreach (StackPanel x in panelList)
    {
        x.Children.Clear();
    }
    this.content.Children.Clear();
    panelList.Clear();
}
 
//calculates how many stackpanels = rows are needed
public void updatePanels()
{
    if (imageList.Count > 0)
    {
        foreach (StackPanel sp in panelList)
            sp.Children.Clear();
 
        panelList.Clear();
 
        double gridWidth = 0;
        if (this.content.ActualWidth == 0)
            gridWidth = 800;
        else
            gridWidth = this.content.ActualWidth;
 
        int gridWidthInt = Convert.ToInt32(gridWidth);
        int imageAmount = imageList.Count;
        int imageMargin = 10;
        int imageWidth = Convert.ToInt32(defaultSize * changeSize);
        int imageSize = imageWidth + 2 * imageMargin;
 
        double ratio = gridWidth / (double)imageSize;
        int ratioInt = Convert.ToInt32(ratio);
        if (ratioInt — ratio > 0)
            ratioInt -= 1;
 
        int newImageMargin = ((gridWidthInt — ratioInt * imageWidth) / ratioInt) / 2;
 
        double panelAmountDouble = (double)imageAmount / ratioInt;
        int panelAmountInt = (int)panelAmountDouble;
 
        if (panelAmountDouble — panelAmountInt > 0)
            panelAmountInt++;
 
        if (panelAmountInt < 1)
            panelAmountInt = 1;
 
        int x = 0;
        for (int i = 0; i < panelAmountInt; i++)
        {
            StackPanel panel = new StackPanel();
            panel.Orientation = Orientation.Horizontal;
            panel.Margin = new Thickness(0, 5, 0, 0);
 
            for (int j = 0; j < ratioInt; j++)
            {
                if (x < imageAmount)
                {
                    imageList[x].Margin = new Thickness(newImageMargin, 0, newImageMargin, 10);
                    imageList[x].changeSize(changeSize);
                    imageList[x].setWallview(this);
 
                    panel.Children.Add(imageList[x]);
                }
                x++;
            }
            panelList.Add(panel);
        }
    }
}
 
//selects all images, gets called when ctrl + a is pressed
public void selectAllImages()
{
    foreach (image i in imageList)
        i.selectImage();
}
 
//unselects all iamges
public void unselectAllImages()
{
    foreach (image i in imageList)
        i.unselectImage();
}
 
//gets called when the slider value changes
private void resizeImages()
{
    updatePanels();
    this.content.Children.Clear();
 
    foreach (StackPanel sp in panelList)
        this.content.Children.Add(sp);
}
 
//method gets called by the slidervaluechanged event
public void changeImageSize(double newsize)
{
    changeSize = newsize;
    resizeImages();
}

На этом шаге мы пишем необходимые обработчики событий для событий, которые мы зарегистрировали в конструкторе ранее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//eventhandler for when the slider gets changed
private void sliderValueChanged(object sender, System.Windows.RoutedPropertyChangedEventArgs<double> e)
{
    changeImageSize(this.slider.Value);
}
 
//eventhandler that gets called when the windowsize changes
private void resizeScrollViewer(object sender, System.Windows.SizeChangedEventArgs e)
{
    resizeImages();
}
 
//eventhandler that unselects all images when you dont click an image
void wallviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    if (!imageSelected)
        unselectAllImages();
 
    else
        imageSelected = false;
}
 
//eventhandler for pressing keys
private void keyDownEvent(object sender, System.Windows.Input.KeyEventArgs e)
{
    if (e.Key == Key.Ctrl)
        ctrlPressed = true;
 
    else if (e.Key == Key.A)
        if (ctrlPressed)
            selectAllImages();
}
 
//eventhandler for releasing keys
private void keyUpEvent(object sender, System.Windows.Input.KeyEventArgs e)
{
    if (e.Key == Key.Ctrl)
        ctrlPressed = false;
}

Теперь мы закончили с wallview-img . Давайте продолжим создавать веб-сервис, который нам понадобится для класса MainPage .


Веб-сервис, который мы собираемся написать, в основном предоставляет нам изображения из определенной папки. Чтобы создать веб-сервис, щелкните правой кнопкой мыши «WallviewApp.Web» в обозревателе решений в правой части Visual Studio и выберите «Добавить»> «Новый элемент» в меню:

Во всплывающем окне выберите «Служба WCF с поддержкой Silverlight Visual C #» и введите «WCF.svc» для имени, затем нажмите «Добавить»:

Как вы можете видеть, мы получили еще один класс под названием WCF с собственным кодом, «WCF.svc.cs».


Добавьте следующие два метода в класс WCF прямо под строкой с надписью « // Add more operations here and mark them with [OperationContract] »:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
//method for getting all the file names within a folder
[OperationContract]
public string[] getFileNames(String dir)
{
    try
    {
        String tmp = HttpContext.Current.Request.MapPath(dir);
        return Directory.GetFiles(@tmp, «*.jpg»);
    }
    catch (Exception)
    {
        return null;
    }
}
 
//method that returns the date of creation of a file or folder
[OperationContract]
public String getFileDates(String file, int i)
{
    return i.ToString() + «-» + File.GetLastWriteTime(file).ToString();
}

Исправьте отсутствующие пространства имен, как мы делали ранее, щелкнув по именам, на которые влияют, затем по синему прямоугольнику и по «импорт …» или добавив эти две строки вручную вверху документа:

1
2
using System.Web;
using System.IO;

Чтобы иметь возможность использовать веб-сервис, нам нужно добавить ссылку на сервис в основной проект. Прежде чем мы сможем это сделать, нам нужно построить проект один раз. Поэтому нажмите «Build» в меню в самом верху Visual Studio, а затем «Build WallviewApp»:


После успешного завершения сборки щелкните правой кнопкой мыши «WallviewApp» с правой стороны в обозревателе решений и выберите «Добавить ссылку на службу» в меню:


В появившемся всплывающем окне нажмите кнопку «Найти» и введите «WCFRef» в поле «Пространство имен», затем нажмите «ОК»:

Без построения проекта перед попыткой добавить ссылку на сервис вы получили бы это сообщение об ошибке:


Откройте файл «MainPage.xaml.cs» и добавьте следующие строки кода над конструктором « public MainPage() »:

1
2
3
4
private WCFRef.WCFClient client;
private string imagedir = «/image/»;
public wallview_img wvi;
private int amountImages;

Конструктор MainPage выглядит следующим образом. Мы инициализируем объявления из предыдущего шага, регистрируем обработчики событий веб-службы и добавляем wallview_img именем » wvi » в Grid » LayoutRoot » LayoutRoot :

01
02
03
04
05
06
07
08
09
10
11
12
13
public MainPage()
{
    InitializeComponent();
    amountImages = 0;
 
    client = new WallviewApp.WCFRef.WCFClient();
    client.getFileNamesCompleted += new EventHandler<WallviewApp.WCFRef.getFileNamesCompletedEventArgs>(client_getFileNamesCompleted);
    client.getFileNamesAsync(imagedir);
    client.getFileDatesCompleted += new EventHandler<WallviewApp.WCFRef.getFileDatesCompletedEventArgs>(client_getFileDatesCompleted);
 
    wvi = new wallview_img();
    this.LayoutRoot.Children.Add(wvi);
}

Добавьте следующие два обработчика событий и метод ниже конструктора MainPage.

« client_getFileNamesCompleted() » получает массив строк, которые возвращаются client_getFileNamesCompleted() . Массив преобразуется в отдельные строки, из которых извлекается имя файла.

Используя адрес localhost , порт, каталог с изображениями и имя файла, мы создаем Uri именем «src». Этот Uri используется для создания нового BitmapImage «bmi», который необходим для создания нового изображения «tmp». Затем изображение «tmp» добавляется к wallview_img «wmi».

После этого метод, который возвращает дату создания файла, вызывается вместе с номером счетчика. Все, что возвращает этот метод, обрабатывается обработчиком client_getFileDatesCompleted (). Поскольку из >2-18.02.2009 12:32:23 is returned from the webservice, we have to split off the counter number at the beginning and the date in the middle. строка в формате >2-18.02.2009 12:32:23 is returned from the webservice, we have to split off the counter number at the beginning and the date in the middle.

Когда эта процедура завершена, конечная дата выглядит как >18.02.2009 and is assigned to the Textblock "imgDate" of the corresponding image.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//event handler for getting the filenames from of the folder
private void client_getFileNamesCompleted(object sender, WallviewApp.WCFRef.getFileNamesCompletedEventArgs e)
{
    if (e.Result != null)
    {
        foreach (string s in e.Result)
        {
            int pos = s.LastIndexOf(«\\»);
            string filename = s.Substring(pos + 1);
            int port = Application.Current.Host.Source.Port;
            Uri src = new Uri(«http://localhost:» + port + imagedir + filename);
            BitmapImage bmi = new BitmapImage(src);
            image tmp = new image(bmi, filename);
            this.wvi.addImage(tmp);
            amountImages++;
 
            getFileDate(s, amountImages — 1);
        }
    }
    else
    {
        MessageBox.Show(«returned null in files completed»);
    }
}
 
//method that calls the webservice asynch with a filepath string and a number so we can assing the returned date string to a certain image again
private void getFileDate(String s, int i)
{
    this.client.getFileDatesAsync(s, i);
}
 
//event handler for getting the filedates
private void client_getFileDatesCompleted(object sender, WallviewApp.WCFRef.getFileDatesCompletedEventArgs e)
{
    if (e.Result != null)
    {
        String dt = e.Result;
 
        int number = Convert.ToInt32(dt.Remove(dt.LastIndexOf(«-«)));
        String date = dt.Remove(dt.LastIndexOf(» «));
        if (number < 10)
            date = date.Remove(0, 2);
        else if (number < 100)
            date = date.Remove(0, 3);
        else
            date = date.Remove(0, 4);
 
        this.wvi.imageList[number].imgDate.Text = date;
    }
    else
    {
        MessageBox.Show(«returned null in dates completed»);
    }
}

Как и в других исходных файлах, пространство имен «BitmapImage» не может быть найдено. Чтобы исправить это, щелкните синий прямоугольник и импортируйте его после нажатия в тексте BitmapImage или BitmapImage добавьте следующую строку в верхнюю часть документа:

1
using System.Windows.Media.Imaging;

Идите вперед и выполните проект, чтобы увидеть, все ли работает. Вы можете сделать это, нажав «F5» на клавиатуре, нажав кнопку с типичным значком «Play» под меню в верхней части панели значков, или выбрав пункт «Начать отладку» внутри «Отладки» Подменю из меню в самой верхней части Visual Studio:

Ваш браузер откроется, и вы получите это сообщение об ошибке:

В чем проблема?

Мы попросили веб-сервис проверить папку «http: // localhost: port / image /» для файлов * .jpg и, очевидно, что ни эта папка, ни изображения в этой папке еще не существуют.


Перейдите в каталог проекта с помощью проводника Windows. Для меня это следующий путь:

Откройте папку «WallviewApp.Web» и создайте в ней новую папку с именем «изображение».

Теперь откройте папку «image» и вставьте в нее несколько * .jpg изображений.


После того, как вы поместили пару изображений в папку изображений, либо нажмите кнопку «Обновить» в веб-браузере (если он все еще открыт), либо просто снова запустите проект, нажав клавишу «F5» в Visual Studio. В результате вы должны увидеть наш финальный Wallview:

Вы можете влиять на размер отображаемых изображений с помощью ползунка на панели навигации.


Мы уже закончили с этим уроком, и я надеюсь, что вам понравилось и вы тоже кое-что узнали

Примерное общее время, которое у меня ушло на разработку этого с нуля, составило около 20 часов. Просмотр альбомов, который вы видите на видео ниже, занял у меня около 15 часов и еще 10 часов, чтобы объединить оба типа.

Вы можете спросить себя, в чем смысл выбора одного или нескольких изображений. Прямо сейчас эта функциональность бесполезна, но я мог бы представить добавление возможности запуска слайд-шоу или создания списка воспроизведения, например, из выбранных изображений. И если вам интересно, почему мы добавили кнопки «Все альбомы», «Текущий альбом», «<» и «>» на панели навигации, но никогда не использовали их …

Мое намерение заключается в разработке другого учебника, который автоматически генерирует фотоальбомы на основе имен файлов изображений. Конечно, этот учебник расширит проект из учебника, который вы только что завершили здесь. Я определенно хотел бы сделать это, если это популярно у аудитории. Чтобы получить представление о том, как будет выглядеть изображение на стене для только что созданных изображений в сочетании с видом на фотоальбомы, посмотрите следующее видео:

Для любых комментариев, предложений или проблем, пожалуйста, оставьте записку в разделе комментариев. Спасибо за прочтение!