В этой статье я хотел бы поделиться небольшим опытом удаленного включения компьютера. Эта тема, пожалуй, многим известна, но хотелось бы еще раз уделить внимание данной технологии. Свою статью я разделю на две части:
  • Сканирование локальной сети, получение IP-адреса, HostName, Mac-address;
  • Создание "magic packet" и отправка.

Вот так примерно выглядит созданная программа:



Итак, приступим к выполнению первого пункта.

1. Сканирование локальной сети: получение IP-адреса, HostName, Mac-address


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

Создадим новый проект в Visual Studio WPF C#. Для начала создадим основной элемент- это ListView, где будет выводится полученная информация.

Код XAML
 <ListView Margin="12,84,12,12" Name="listView1">
            <ListView.ContextMenu>
                <ContextMenu >
                    <MenuItem Header="Копировать">
                        <MenuItem Header="IP-adress" Name="copyIP" Click="copyIP_Click" />
                        <MenuItem Header="HostName" Name="copyName" Click="copyName_Click" />
                        <MenuItem Header="Mac-adress" Name="copyMacAddress" Click="copyMacAddress_Click" />
                    </MenuItem>
                    <MenuItem Header="Очистить список" Name="ClearList" Click="ClearList_Click" />
                    <MenuItem Header="Сохранить список"/>
                    <MenuItem Header="Действия">
                        <MenuItem Header="Удаленное включение" Name="PowerOn" Click="PowerOn_Click" />
                        <MenuItem Header="Удаленное завершение" IsEnabled="False" />
                    </MenuItem>
                    <MenuItem Header="Отмена"/>
                </ContextMenu>
            </ListView.ContextMenu>
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn Header="IP-Адрес" DisplayMemberBinding="{Binding Path=ipAdress}" Width="200"></GridViewColumn>
                        <GridViewColumn Header="HostName" DisplayMemberBinding="{Binding Path=nameComputer}" Width="250"></GridViewColumn>
                        <GridViewColumn Header="Mac адрес" DisplayMemberBinding="{Binding Path=MacAdress}" Width="250"></GridViewColumn>
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>

Код C#
 class TableHost
    {
        public string ipAdress { get; set; }
        public string nameComputer { get; set; }
        public string MacAdress { get; set; }
    }

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

Код C#

 // Получение ip-адреса.
            System.Net.IPAddress ip = System.Net.Dns.GetHostByName(host).AddressList[0];

 private void button2_Click(object sender, RoutedEventArgs e)
        {
            int i = int.Parse(ipToString[0]);
            int j = int.Parse(ipToString[1]);
                {
                    for(int k = 0;k<6;k++)
                    {
                        for (int m = 0; m < 254; m++)
                        {
                             //Запускаем проверку в новом потоке
                            Thread _thread = new Thread(() => GetInform(string.Format("{0}.{1}.{2}.{3}", i.ToString(), j.ToString(), k.ToString(), m.ToString())));
                            _thread.Start();                            
                        }
                    }
                }
               
        }
 private void GetInform(string textName)
        {
            string IP_Address = "";
            string HostName = "";
            string MacAddress = "";

            try
            {
                //Проверяем существует ли IP
                entry = Dns.GetHostEntry(textName);
                foreach (IPAddress a in entry.AddressList)
                {
                    IP_Address = a.ToString();
                    break;
                }

                //Получаем HostName
                HostName = entry.HostName;

                //Получаем Mac-address
                IPAddress dst = IPAddress.Parse(textName); 

                byte[] macAddr = new byte[6];
                uint macAddrLen = (uint)macAddr.Length;

                if (SendARP(BitConverter.ToInt32(dst.GetAddressBytes(), 0), 0, macAddr, ref macAddrLen) != 0)
                    throw new InvalidOperationException("SendARP failed.");

                string[] str = new string[(int)macAddrLen];
                for (int i = 0; i < macAddrLen; i++)
                    str[i] = macAddr[i].ToString("x2");

                MacAddress = string.Join(":", str);

                //Далее, если всё успешно, добавляем все данные в список, после чего выводим всё в ListView
                Dispatcher.Invoke(new Action(() =>
                {

                    _host.Add(new TableHost() { ipAdress = IP_Address, nameComputer = HostName, MacAdress = MacAddress });
                    listView1.ItemsSource = null;
                    listView1.ItemsSource = _host;
                }));
            }
            catch { }
           
        }

Теперь мы имеем всё, что нам необходимо. Приступаем к следующему этапу.

2. Создание «magic packet» и отправка


С определением вы можете ознакомится здесь. Этот этап, как вы сами понимаете, будет работать только с выключенным компьютером, так что нам необходимо будет сохранить всю информацию. Полный код я напишу в конце статьи.

Код на самом деле не такой то уж и страшный, деятельность этого пункта можно разделить на два этапа: создание пакета и его отправка.

Код C#

        [DllImport("iphlpapi.dll", ExactSpelling = true)]
        public static extern int SendARP(int destIp, int srcIP, byte[] macAddr, ref uint physicalAddrLen);
private void WakeFunction(string MAC_ADDRESS)
        {
            WOLClass client = new WOLClass();
            client.Connect(new IPAddress(0xffffffff), 0x2fff);
            client.SetClientToBrodcastMode();
            int counter = 0;
            //буфер для отправки
            byte[] bytes = new byte[1024];
            //Первые 6 бит 0xFF
            for (int y = 0; y < 6; y++)
                bytes[counter++] = 0xFF;
            //Повторим MAC адрес 16 раз
            for (int y = 0; y < 16; y++)
            {
                int i = 0;
                for (int z = 0; z < 6; z++)
                {
                    bytes[counter++] = byte.Parse(MAC_ADDRESS.Substring(i, 2), NumberStyles.HexNumber);
                    i += 2;
                }
            }

            //Отправляем полученный магический пакет
            int reterned_value = client.Send(bytes, 1024);
        }
 public class WOLClass : UdpClient
    {
        public WOLClass()
            : base()
        { }
        //Установим broadcast для отправки сообщений
        public void SetClientToBrodcastMode()
        {
            if (this.Active)
                this.Client.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.Broadcast, 0);
        }
    }

Вот, наверное, и всё, я постарался пробежаться по основным моментам несложной, но очень интересной программы. Для статьи я наполнил эту программу еще некоторым функционалом, чтобы как-то разнообразить ее. Если кого то заинтересует — здесь есть, что взять для себя. Ниже представлен весь код:

Код XAML
<Window x:Class="Network.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="497" Width="872" Closing="Window_Closing">
    <Grid>
        <ListView Margin="12,84,12,12" Name="listView1">
            <ListView.ContextMenu>
                <ContextMenu >
                    <MenuItem Header="Копировать">
                        <MenuItem Header="IP-adress" Name="copyIP" Click="copyIP_Click" />
                        <MenuItem Header="HostName" Name="copyName" Click="copyName_Click" />
                        <MenuItem Header="Mac-adress" Name="copyMacAddress" Click="copyMacAddress_Click" />
                    </MenuItem>
                    <MenuItem Header="Очистисть список" Name="ClearList" Click="ClearList_Click" />
                    <MenuItem Header="Сохранить список"/>
                    <MenuItem Header="Действия">
                        <MenuItem Header="Удаленное включение" Name="PowerOn" Click="PowerOn_Click" />
                        <MenuItem Header="Удаленное завершение" IsEnabled="False" />
                    </MenuItem>
                    <MenuItem Header="Отмена"/>
                </ContextMenu>
            </ListView.ContextMenu>
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn Header="IP-Адрес" DisplayMemberBinding="{Binding Path=ipAdress}" Width="200"></GridViewColumn>
                        <GridViewColumn Header="HostName" DisplayMemberBinding="{Binding Path=nameComputer}" Width="250"></GridViewColumn>
                        <GridViewColumn Header="Mac адрес" DisplayMemberBinding="{Binding Path=MacAdress}" Width="250"></GridViewColumn>
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>
        <TextBox Height="23" HorizontalAlignment="Left" Margin="221,15,0,0" Name="textBox1" VerticalAlignment="Top" Width="164" />
        <Label  Height="27" HorizontalAlignment="Left" Margin="219,36,0,0" Name="label1" VerticalAlignment="Top" Width="316">
            IP-адресс/Host-имя компьютера,
            
                например: 192.168.1.1
        </Label>
        <Button Content="Начать сканирование" Height="52" HorizontalAlignment="Left" Margin="12,15,0,0" Name="button2" VerticalAlignment="Top" Width="153" Click="button2_Click" />
        <Label Content="Ваш IP:" Height="28" HorizontalAlignment="Right" Margin="0,1,125,0" Name="label2" VerticalAlignment="Top" Width="50" />
        <Label Height="28" Margin="0,1,2,0" Name="label3" VerticalAlignment="Top" HorizontalAlignment="Right" Width="117" />
        <Button Content="Проверить" Height="23" HorizontalAlignment="Left" Margin="415,15,0,0" Name="button1" VerticalAlignment="Top" Width="120" Click="button1_Click_1" />
        <ComboBox Height="23" HorizontalAlignment="Right" Margin="0,45,62,0" Name="comboBox1" VerticalAlignment="Top" Width="199" SelectionChanged="comboBox1_SelectionChanged" />
        <Grid Background="#FF9D9D9D" HorizontalAlignment="Right" Margin="0,91,-191,10" Name="rectangle1" Width="178">
            <Label Content="IP- адрес" Height="28" Margin="16,25,23,0" Name="label4" VerticalAlignment="Top" />
            <TextBox Height="23" HorizontalAlignment="Left" Margin="16,53,0,0" Name="textBox2" VerticalAlignment="Top" Width="139" />
            <TextBox Height="23" HorizontalAlignment="Left" Margin="16,124,0,0" Name="textBox3" VerticalAlignment="Top" Width="139" />
            <Label Content="HostName" Height="28" Margin="16,90,23,0" Name="label5" VerticalAlignment="Top" />
            <TextBox Height="23" HorizontalAlignment="Left" Margin="16,203,0,0" Name="textBox4" VerticalAlignment="Top" Width="139" />
            <Label Content="Mac-address" Height="28" Margin="16,169,23,0" Name="label6" VerticalAlignment="Top" />
            <Button Content="Включить" Height="23" HorizontalAlignment="Left" Margin="16,0,0,58" Name="button3" VerticalAlignment="Bottom" Width="139" Click="button3_Click" />
            <Button Content="Закрыть" Height="23" HorizontalAlignment="Left" Margin="16,0,0,19" Name="button4" VerticalAlignment="Bottom" Width="139" Click="button4_Click" />
        </Grid>
    </Grid>
</Window>



код C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Net.Sockets;
using System.Globalization;
using System.IO;

namespace Network
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        [DllImport("iphlpapi.dll", ExactSpelling = true)]
        public static extern int SendARP(int destIp, int srcIP, byte[] macAddr, ref uint physicalAddrLen);

        List<TableHost> _host = new List<TableHost>();
        string hostname = "";
        IPHostEntry entry ;
        string[] ipToString = new string[4];


        public MainWindow()
        {
            InitializeComponent();
            
                //string[] first = adress.ToString().Split('.');
                //string[] second = IPAddress.Parse("192.168.1.0").ToString().Split('.');
            // Получение имени компьютера.
            String host = System.Net.Dns.GetHostName();
            // Получение ip-адреса.
            System.Net.IPAddress ip = System.Net.Dns.GetHostByName(host).AddressList[0];
            // Показ адреса в label'е.
            label3.Content = ip.ToString();
            ipToString = ip.ToString().Split('.');
            ADd();
        }

        string[] ipadressText;
        string[] hostnameText;
        string[] macaddressText;

        private void ADd()
        {
            string[] str = File.ReadAllLines("IPMAC.txt");
            ipadressText = new string[str.Length];
            hostnameText = new string[str.Length];
            macaddressText = new string[str.Length];
            for (int i = 0; i < str.Length; i++)
            {
                string[] s = str[i].Split('#');
                ipadressText[i] = s[0];
                hostnameText[i] = s[1];
                macaddressText[i] = s[2];
                comboBox1.Items.Add(s[1]);
            }
            
        }

        private void WakeFunction(string MAC_ADDRESS)
        {
            WOLClass client = new WOLClass();
            client.Connect(new IPAddress(0xffffffff), 0x2fff);
            client.SetClientToBrodcastMode();
            int counter = 0;
            //буффер для отправки
            byte[] bytes = new byte[1024];
            //Первые 6 бит 0xFF
            for (int y = 0; y < 6; y++)
                bytes[counter++] = 0xFF;
            //Повторим MAC адрес 16 раз
            for (int y = 0; y < 16; y++)
            {
                int i = 0;
                for (int z = 0; z < 6; z++)
                {
                    bytes[counter++] = byte.Parse(MAC_ADDRESS.Substring(i, 2), NumberStyles.HexNumber);
                    i += 2;
                }
            }

            //Отправляем полученый магический пакет
            int reterned_value = client.Send(bytes, 1024);
        }


        private void GetInform(string textName)
        {
            string IP_Address = "";
            string HostName = "";
            string MacAddress = "";

            try
            {
                //Проверяем существует ли IP
                entry = Dns.GetHostEntry(textName);
                foreach (IPAddress a in entry.AddressList)
                {
                    IP_Address = a.ToString();
                    break;
                }

                //Получаем HostName
                HostName = entry.HostName;

                //Получаем Mac-address
                IPAddress dst = IPAddress.Parse(textName); 

                byte[] macAddr = new byte[6];
                uint macAddrLen = (uint)macAddr.Length;

                if (SendARP(BitConverter.ToInt32(dst.GetAddressBytes(), 0), 0, macAddr, ref macAddrLen) != 0)
                    throw new InvalidOperationException("SendARP failed.");

                string[] str = new string[(int)macAddrLen];
                for (int i = 0; i < macAddrLen; i++)
                    str[i] = macAddr[i].ToString("x2");

                MacAddress = string.Join(":", str);

                //Далее, если всё успешно, добавляем все данные в список, после чего выводим всё в ListView
                Dispatcher.Invoke(new Action(() =>
                {

                    _host.Add(new TableHost() { ipAdress = IP_Address, nameComputer = HostName, MacAdress = MacAddress });
                    listView1.ItemsSource = null;
                    listView1.ItemsSource = _host;
                }));
            }
            catch { }
           
        }


        private void button1_Click(object sender, RoutedEventArgs e)
        {

            //string message = "";
            //Print(GetIP(textBox1.Text), GetHostName(textBox1.Text), GetMac(message));
        }

        private void button2_Click(object sender, RoutedEventArgs e)
        {
            int i = int.Parse(ipToString[0]);
            int j = int.Parse(ipToString[1]);
                {
                    for(int k = 0;k<6;k++)
                    {
                        for (int m = 0; m < 254; m++)
                        {
                            Thread _thread = new Thread(() => GetInform(string.Format("{0}.{1}.{2}.{3}", i.ToString(), j.ToString(), k.ToString(), m.ToString())));
                            _thread.Start();                            
                        }
                    }
                }
               
        }

        private void PowerOn_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                WakeFunction(_host[listView1.SelectedIndex].MacAdress.ToString().Replace(":", ""));
                MessageBox.Show("Операция выполнена успешно!", "Внимание!", MessageBoxButton.OK, MessageBoxImage.Information);
            }
            catch { MessageBox.Show("Запрос некорретный!","Внимание!Ошибка!",MessageBoxButton.OK,MessageBoxImage.Error); }
        }

        private void ClearList_Click(object sender, RoutedEventArgs e)
        {
            _host.Clear();
            listView1.Items.Refresh();
        }

        private void copyIP_Click(object sender, RoutedEventArgs e)
        {
            Clipboard.SetText(_host[listView1.SelectedIndex].ipAdress.ToString());
        }

        private void copyName_Click(object sender, RoutedEventArgs e)
        {
            Clipboard.SetText(_host[listView1.SelectedIndex].nameComputer.ToString());
        }

        private void copyMacAddress_Click(object sender, RoutedEventArgs e)
        {
            Clipboard.SetText(_host[listView1.SelectedIndex].MacAdress.ToString());
        }

        private void button1_Click_1(object sender, RoutedEventArgs e)
        {
            GetInform(textBox1.Text);
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            StreamWriter write = new StreamWriter(@"C:\Users\***\IPMAC.txt", true);

            for (int index = 0; index < _host.Count; index++)
            {

                if (!macaddressText.Contains(_host[index].MacAdress))
                    write.WriteLine(_host[index].ipAdress + "#" + _host[index].nameComputer + "#" + _host[index].MacAdress);
            }
            write.Close();
        }

        void P()
        {
            for (double i = -192; i < 10; i += 0.004)
            {
                Dispatcher.Invoke(new Action(() =>
                {
                    rectangle1.Margin = new Thickness(0, 101, i, 10);
                }));

            }//12,0,12,1


        }

        private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            //ii = rectangle1.Width;
            textBox2.Text = ipadressText[comboBox1.SelectedIndex];
            textBox3.Text = comboBox1.SelectedItem.ToString();
            textBox4.Text = macaddressText[comboBox1.SelectedIndex];
            Thread thread = new Thread(P);
            thread.Start();
        }

        private void button3_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                WakeFunction(textBox4.Text.Replace(":", ""));
                MessageBox.Show("Операция выполнена успешно!", "Внимание!", MessageBoxButton.OK, MessageBoxImage.Information);
            }
            catch { MessageBox.Show("Запрос некорретный!", "Внимание!Ошибка!", MessageBoxButton.OK, MessageBoxImage.Error); }
        }

        private void button4_Click(object sender, RoutedEventArgs e)
        {
            Thread thread = new Thread(CLose);
            thread.Start();
        }

        void CLose()
        {
            for (double i = 9; i > -193; i -= 0.004)
            {
                Dispatcher.Invoke(new Action(() =>
                {
                    rectangle1.Margin = new Thickness(0, 101, i, 10);
                }));

            }//12,0,12,1
        }
    }

    class TableHost
    {
        public string ipAdress { get; set; }
        public string nameComputer { get; set; }
        public string MacAdress { get; set; }
    }

    public class WOLClass : UdpClient
    {
        public WOLClass()
            : base()
        { }
        //Установим broadcast для отправки сообщений
        public void SetClientToBrodcastMode()
        {
            if (this.Active)
                this.Client.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.Broadcast, 0);
        }
    }
}


Комментарии (32)


  1. meft
    15.07.2015 17:00
    +2

    Ну раз тема называется «Удаленное включение по Mac-адресу C# (Wake On Lan)», а не что-то вроде «код моей программы для включения ПК», то, полагаю, Вы достаточно хорошо разобрались и в самой теме «Wake On Lan».
    В связи с этим просьба подсказать мне,
    1) компьютер может быть именно выключен, или же должен находится в спящем режиме?
    2) каким образом организовать включение ПК через интернет, а точнее через роутер, при условии, что роутер не поддерживает специальных функций для «Wake On Lan». Т.е. на примере бюджетных роутеров.


    1. HomoLuden
      15.07.2015 17:03
      +4

      В связи с этим могу предложить более надежный способ.

      — Дорогая, ты дома?
      — Да…
      — Супер! Жмякни кнопку на системнике, плииииз.
      — А моник включать? Неее… не нужно. Спасибки, целую, до вечера!


      1. meft
        15.07.2015 17:07

        Проблема кроется во второй строчке диалога :)


        1. HomoLuden
          15.07.2015 17:15
          +1

          ХеХе Тогда уж начнем с проблемы наличия жены…

          Да, согласен, есть такой шанс, что не будет дома человека. Но также может произойти сбой в электросети, из-за чего ПК будет в состоянии, не способном обработать WoL.
          Вот ежели реализовать некий аппаратный включатель с подключением к сети… например на МК с модулем WiFi <=> UART. Теоретически можно будет на МК заслать команду включения (текст, известный только хозяйствующему параноику, если угодно).
          Возможный вариант: некое веб-приложение, опрашиваемое микроконтроллером с некоей периодичностью (скажем, раз в 10 сек. или минуту).


          1. MooM_IYD
            16.07.2015 11:40

            Может интересно будет решение «Без жены».
            habrahabr.ru/post/249507


      1. pashagoroshko Автор
        15.07.2015 17:56

        эту программу я написал живя в общежитии университета, так что я преследовал идею по включению в первую очередь чужих компов удаленно.


    1. Mu57Di3
      15.07.2015 17:35
      -1

      Я решил вторую проблему довольно просто, помимо компа дома еще в сети есть Raspberry Pi в которой висит в кроне скрипт с пингом компьютера который должен быть всегда включен и если он не отзывается то посылает магический патетик. Причем добавлено еще и ограничение на количество попыток, а то мало ли с компом чего не так и тогда мне шлется письмо на электронку.


      1. BuriK666
        15.07.2015 20:23
        +1

        Зачем это? если можно просто не выключать ПК и в биосе указать Restore on AC Power Loss: Last State / Power On?


        1. pashagoroshko Автор
          16.07.2015 00:17

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


          1. BuriK666
            16.07.2015 00:30

            К статье претензий нет. у меня вопрос к Mu57Di3, зачем прикручивать Raspberry, пинговалку и т.п?


            1. Mu57Di3
              16.07.2015 11:37

              Малинка выполняет еще много разных функций, а так как она все равно болтается в сети вот не нее и повесил еще и эту функцию.


          1. homecreate
            18.07.2015 15:57

            Несмешные шутки какие-то у вас в общежитии


        1. Mu57Di3
          16.07.2015 11:37

          Я был огорчен но в биосе материнской платы нет этой функции.


    1. AxisPod
      15.07.2015 17:51

      Купил себе Mikrotik, поставил, проблем не ощущаю. Будит всё что надо без напрягов, ну и когда надо.


    1. stychos
      16.07.2015 13:00

      1) может быть именно выключен, но должен поддерживать WoL
      2) если человеку действительно необходимо включать машину через интернет, то стоит подумать о нормальном роутере, костыльные варианты также применимы, например — наличие туннеля или иного доступа к промежуточной всегда работающей машине


    1. Namelles_One
      17.07.2015 15:15

      1. Выключен.
      2. С совсем дешевыми роутерами, полагаю, никак. Если на роутер есть ssh, то тогда в дело вступает скрипт на перле, который отлично вэйкапит компы.


  1. HomoLuden
    15.07.2015 17:18

    [zanuda_on] XAML — это разметка. Все равно, что при итальянце обозвать спагетти рожками, например. [/zanuda_on]


    1. HomoLuden
      15.07.2015 17:44
      +2

      Замечания к разметке и архитектуре в целом.
      Я понимаю так, что данная реализация скорее всего драфтовый набросок или «proof-of-concept». Но даже для таких целей советую:

      • Избавляться от лени и давать элементам интерфейса осмысленные имена (вместо button2 — StartScanButton — или Buttons_StartScan). Последняя конвенция наименования мне не нравится, т.е. идет в разрез с C# Naming Convention, но в разметке некоторым коллегам нравится такой тэггинг и группировка.
      • Имя элемента задавать через x:Name (в некоторых случаях просто Name некорректно обрабатывается). Ну и располагать x:Name сразу после тэка элемента на той же строке. Тогда если вы схлопните тэг в разметке — вы все равно будете видеть имя.
      • Вместо обработчика событий в Code Behind, все таки реализовать MVVM + ICommand.


      Замечания к коду:
      label3.Content = ip.ToString();
      ipToString = ip.ToString().Split('.');
      

      Два раза один и тот же код вызывается. Можно можно закэшировать строку и Split() вызвать на кэше (локальной переменной).

      
              string[] ipadressText;
              string[] hostnameText;
              string[] macaddressText;
      
      

      Не по конвенции C# приватные поля названы. И располагаться они должны в верху. Также с конвенцией наименования есть нарушения и в других местах. Напр., «private void WakeFunction(string MAC_ADDRESS)» (имя параметра).

      private void ADd()
      

      Опечатка в имени?

      Если бы вы использовали MVVM, то вам не пришлось бы врукопашную очищать и устанавливать в новое значение ItemsSource, а также вызывать явно Items.Refresh().

      StreamWriter write = new StreamWriter(@"C:\Users\***\IPMAC.txt", true);
      
                  for (int index = 0; index < _host.Count; index++)
                  {
      
                      if (!macaddressText.Contains(_host[index].MacAdress))
                          write.WriteLine(_host[index].ipAdress + "#" + _host[index].nameComputer + "#" + _host[index].MacAdress);
                  }
                  write.Close();
      

      Лучше так:

      using(var write = new StreamWriter(@"C:\Users\***\IPMAC.txt", true))
      {
          // writer usage goes here
          // no need to call Close explicitly
      }
      


      1. pashagoroshko Автор
        15.07.2015 17:54

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


        1. HomoLuden
          15.07.2015 18:12
          +3

          Когда публикуете код, особенно если это на Хабре, имейте в виду, что велик шанс, что никто не догадается, что в мыслях у вас была установка, что код этот очень-очень черновой. Скопипастят новички и будут тиражировать дальше.


          1. DjoNIK
            16.07.2015 11:28

            Но с другой стороны, при излишествах в виде полноценного MVVM (с разнесением каждого из слоев по разным проектам) потеряется суть описываемого подхода и фокус сместится. Тем же новичкам будет тяжело понять и они «забьют».


            1. HomoLuden
              16.07.2015 11:54

              Не обязательно разносить по слоям на сборки. Подкаталоги в проекте как раз удовлетворяют драфтовости приложения и не нарушают требования культуры.
              Как только свыкаетесь с MVVM, написание кода становится не намного более медленным. На автомате все пишется. Если еще подключите сборки Expression Blend Interaction, то и ICommand станут не нужны. Можно будет из XAML вызывать публичные методы в ViewModel.

              Кроме того, отделив логику от UI, Вы еще больше сконцентрируете внимание читателя чисто на логике.


              1. DjoNIK
                16.07.2015 12:24

                Я-то как раз предпочитаю в рабочих проектах следовать практике этого паттерна. Но в тестовых проектах, обучающих примерах чаще всего не использую.

                Если еще подключите сборки Expression Blend Interaction

                Мне не нравится та монструозность в разметке, которую привносит Interaction. Я настолько полюбил Caliburn.Micro, что от Interaction-ов в xaml впадаю в депрессию ))


      1. Iceg
        15.07.2015 22:44

        Спасибо вам за комментарий. По-моему, вот так, с разбором ошибок, даже полезней получается, чем изначально был бы «православный» код :)

        ps
        А ещё там во втором листинге ipAdress без одной буквы =(


        1. pashagoroshko Автор
          15.07.2015 23:36

          Главное вы таких «грубых» ошибок не допускайте))))
          Извиняюсь))


          1. Iceg
            16.07.2015 01:30

            Не допускаю :0) Ну вот когда intellisense и всё такое, то почти пофиг, но бывают специфические ситуации, когда волею судьбы на С и в убогом редакторе без автодополнения достаётся чужой код, где сплошь вот такие «adress» и прочие «devise» — это, скажем, не делает мир лучше, а сопровождение — проще. Вы даже опечатку в имени метода SetClientToBroadcastMode перенесли к себе. CodeNazi негодуэ ;D


            1. HomoLuden
              16.07.2015 12:05
              +2

              Это не CodeNazi, и не только культура программирования.
              1. Naming Convention нужна, чтоб ускорить понимание кода.
              2. Орфография и грамматика английского в C# также важна для понимания того, что делает метод например. Писать комментарии может быть долше, чем адекватное название для метода, например.

              Вот например для инстанциирования ICommand может потребоваться реализовать метод выдающий bool (активация и деактивация команды). Как его назвать? Параметры конструктора обзываются: execute и canExecute.

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

              А назовете их например так: HandlerOfBlaBlaCommand и IsBlaBlaCommandEnabled
              сразу вопросы появляются. Последний метод по форме названия на свойство больше похож. А первый метод на делегат смахивает. Тут что имеет место подписка на событие? А что там с утечками?

              Кроме того, если оба метода начинаются одинаково, то они в навигаторе по методам группируются.
              BlaBlaCommand (get; set)
              BlaBlaCommandExecute
              BlaBlaCommandCanExecute

              Потому я счтаю граматически привильное наименование очень важным при коммандной разработке.


  1. NeoNN
    16.07.2015 09:56

    StyleCop и решарпер очень помогают, не стоит ими пренебрегать при написании кода. И да, MVVM!


    1. HomoLuden
      16.07.2015 12:08

      Кстати, на практике все же приходится в Code Behind уходить для реализации некоего специфичного поведения (спасибо UX спецам). Но для этого удобно Expression Blend Behavior<...> использовать.


      1. NeoNN
        16.07.2015 12:38

        Dependency и Attached Properties именно для этого и придуманы ;-) Behaviors да, очень мощный и полезный механизм.


        1. HomoLuden
          16.07.2015 18:29

          У AttachedProperty очень сложный жизненный цикл. Разработчику в статическом сеттере необходимо создавать инстанс текужего же класса, приаттачивать его еще одним статическим сеттером к контролу и забывать референс на только что созданный инстанс, либо класть его в статическое поле. Если к этому прибавляется еще и подписка на событие, то часто используется статически определенный делегат (привет утечки).
          А Behavior<..> прост в этом отношении. Точка входа — OnAttached(...). Работаешь только с конкретными инстансами носителя и самого Behavior. Опять же в Behavior Вы вольны пользоваться хоть DP, хоть обычными свойствами. Единственный недостаток Behavior, что его нельзя в явном виде прицепить в сеттере стилей, например.


  1. impwx
    16.07.2015 15:10
    +1

    Статью можно было вполне ограничить кодом методов WakeFunction и GetInform, реализовав их в виде мини-библиотеки. Всё остальное, включая разметку и codebehind — смысловой нагрузки по теме статьи не несет, поэтому лучше это выложить на Github.